mutt stable branch with some hacks
at master 389 lines 10 kB view raw
1/* 2 * Copyright (C) 2005 Andreas Krennmair <ak@synflood.at> 3 * Copyright (C) 2005 Peter J. Holzer <hjp@hjp.net> 4 * Copyright (C) 2005-2009 Rocco Rutte <pdmef@gmx.net> 5 * Copyright (C) 2010 Michael R. Elkins <me@mutt.org> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 * 21 */ 22 23/* This file was originally part of mutt-ng */ 24 25#if HAVE_CONFIG_H 26# include "config.h" 27#endif 28 29#include <stdlib.h> 30#include <string.h> 31#include <unistd.h> 32#include <ctype.h> 33#include <sys/wait.h> 34#include <sys/stat.h> 35 36#include "mutt.h" 37#include "mutt_curses.h" 38#include "ascii.h" 39#include "lib.h" 40 41#define FLOWED_MAX 72 42 43typedef struct flowed_state 44{ 45 size_t width; 46 size_t spaces; 47 int delsp; 48} flowed_state_t; 49 50static int get_quote_level (const char *line) 51{ 52 int quoted = 0; 53 char *p = (char *) line; 54 55 while (p && *p == '>') 56 { 57 quoted++; 58 p++; 59 } 60 61 return quoted; 62} 63 64/* Determines whether to add spacing between/after each quote level: 65 * >>>foo 66 * becomes 67 * > > > foo 68 */ 69static int space_quotes (STATE *s) 70{ 71 /* Allow quote spacing in the pager even for OPTTEXTFLOWED, 72 * but obviously not when replying. 73 */ 74 if (option (OPTTEXTFLOWED) && (s->flags & MUTT_REPLYING)) 75 return 0; 76 77 return option (OPTREFLOWSPACEQUOTES); 78} 79 80/* Determines whether to add a trailing space to quotes: 81 * >>> foo 82 * as opposed to 83 * >>>foo 84 */ 85static int add_quote_suffix (STATE *s, int ql) 86{ 87 if (s->flags & MUTT_REPLYING) 88 return 0; 89 90 if (space_quotes (s)) 91 return 0; 92 93 if (!ql && !s->prefix) 94 return 0; 95 96 /* The prefix will add its own space */ 97 if (!option (OPTTEXTFLOWED) && !ql && s->prefix) 98 return 0; 99 100 return 1; 101} 102 103static size_t print_indent (int ql, STATE *s, int add_suffix) 104{ 105 int i; 106 size_t wid = 0; 107 108 if (s->prefix) 109 { 110 /* use given prefix only for format=fixed replies to format=flowed, 111 * for format=flowed replies to format=flowed, use '>' indentation 112 */ 113 if (option (OPTTEXTFLOWED)) 114 ql++; 115 else 116 { 117 state_puts (s->prefix, s); 118 wid = mutt_strwidth (s->prefix); 119 } 120 } 121 for (i = 0; i < ql; i++) 122 { 123 state_putc ('>', s); 124 if (space_quotes (s) ) 125 state_putc (' ', s); 126 } 127 if (add_suffix) 128 state_putc (' ', s); 129 130 if (space_quotes (s)) 131 ql *= 2; 132 133 return ql + add_suffix + wid; 134} 135 136static void flush_par (STATE *s, flowed_state_t *fst) 137{ 138 if (fst->width > 0) 139 { 140 state_putc ('\n', s); 141 fst->width = 0; 142 } 143 fst->spaces = 0; 144} 145 146/* Calculate the paragraph width based upon the current quote level. The start 147 * of a quoted line will be ">>> ", so we need to subtract the space required 148 * for the prefix from the terminal width. */ 149static int quote_width (STATE *s, int ql) 150{ 151 int width = mutt_window_wrap_cols (MuttIndexWindow, ReflowWrap); 152 if (option(OPTTEXTFLOWED) && (s->flags & MUTT_REPLYING)) 153 { 154 /* When replying, force a wrap at FLOWED_MAX to comply with RFC3676 155 * guidelines */ 156 if (width > FLOWED_MAX) 157 width = FLOWED_MAX; 158 ++ql; /* When replying, we will add an additional quote level */ 159 } 160 /* adjust the paragraph width subtracting the number of prefix chars */ 161 width -= space_quotes (s) ? ql*2 : ql; 162 /* When displaying (not replying), there may be a space between the prefix 163 * string and the paragraph */ 164 if (add_quote_suffix (s, ql)) 165 --width; 166 /* failsafe for really long quotes */ 167 if (width <= 0) 168 width = FLOWED_MAX; /* arbitrary, since the line will wrap */ 169 return width; 170} 171 172static void print_flowed_line (char *line, STATE *s, int ql, 173 flowed_state_t *fst, int term) 174{ 175 size_t width, w, words = 0; 176 char *p; 177 char last; 178 179 if (!line || !*line) 180 { 181 /* flush current paragraph (if any) first */ 182 flush_par (s, fst); 183 print_indent (ql, s, 0); 184 state_putc ('\n', s); 185 return; 186 } 187 188 width = quote_width (s, ql); 189 last = line[mutt_strlen (line) - 1]; 190 191 dprint (4, (debugfile, "f=f: line [%s], width = %ld, spaces = %d\n", 192 NONULL(line), (long)width, fst->spaces)); 193 194 for (p = (char *)line, words = 0; (p = strsep (&line, " ")) != NULL ; ) 195 { 196 dprint(4,(debugfile,"f=f: word [%s], width: %d, remaining = [%s]\n", 197 p, fst->width, line)); 198 199 /* remember number of spaces */ 200 if (!*p) 201 { 202 dprint(4,(debugfile,"f=f: additional space\n")); 203 fst->spaces++; 204 continue; 205 } 206 /* there's exactly one space prior to every but the first word */ 207 if (words) 208 fst->spaces++; 209 210 w = mutt_strwidth (p); 211 /* see if we need to break the line but make sure the first 212 word is put on the line regardless; 213 if for DelSp=yes only one trailing space is used, we probably 214 have a long word that we should break within (we leave that 215 up to the pager or user) */ 216 if (!(!fst->spaces && fst->delsp && last != ' ') && 217 w < width && w + fst->width + fst->spaces > width) 218 { 219 dprint(4,(debugfile,"f=f: break line at %d, %d spaces left\n", 220 fst->width, fst->spaces)); 221 /* only honor trailing spaces for format=flowed replies */ 222 if (option(OPTTEXTFLOWED)) 223 for ( ; fst->spaces; fst->spaces--) 224 state_putc (' ', s); 225 state_putc ('\n', s); 226 fst->width = 0; 227 fst->spaces = 0; 228 words = 0; 229 } 230 231 if (!words && !fst->width) 232 fst->width = print_indent (ql, s, add_quote_suffix (s, ql)); 233 fst->width += w + fst->spaces; 234 for ( ; fst->spaces; fst->spaces--) 235 state_putc (' ', s); 236 state_puts (p, s); 237 words++; 238 } 239 240 if (term) 241 flush_par (s, fst); 242} 243 244static void print_fixed_line (const char *line, STATE *s, int ql, 245 flowed_state_t *fst) 246{ 247 print_indent (ql, s, add_quote_suffix (s, ql)); 248 if (line && *line) 249 state_puts (line, s); 250 state_putc ('\n', s); 251 252 fst->width = 0; 253 fst->spaces = 0; 254} 255 256int rfc3676_handler (BODY * a, STATE * s) 257{ 258 char *buf = NULL, *t = NULL; 259 unsigned int quotelevel = 0, newql = 0, sigsep = 0; 260 int buf_off = 0, delsp = 0, fixed = 0; 261 size_t buf_len = 0, sz = 0; 262 flowed_state_t fst; 263 264 memset (&fst, 0, sizeof (fst)); 265 266 /* respect DelSp of RfC3676 only with f=f parts */ 267 if ((t = (char *) mutt_get_parameter ("delsp", a->parameter))) 268 { 269 delsp = mutt_strlen (t) == 3 && ascii_strncasecmp (t, "yes", 3) == 0; 270 t = NULL; 271 fst.delsp = 1; 272 } 273 274 dprint (4, (debugfile, "f=f: DelSp: %s\n", delsp ? "yes" : "no")); 275 276 while ((buf = mutt_read_line (buf, &sz, s->fpin, NULL, 0))) 277 { 278 buf_len = mutt_strlen (buf); 279 newql = get_quote_level (buf); 280 281 /* end flowed paragraph (if we're within one) if quoting level 282 * changes (should not but can happen, see RFC 3676, sec. 4.5.) 283 */ 284 if (newql != quotelevel) 285 flush_par (s, &fst); 286 287 quotelevel = newql; 288 buf_off = newql; 289 290 /* respect sender's space-stuffing by removing one leading space */ 291 if (buf[buf_off] == ' ') 292 buf_off++; 293 294 /* test for signature separator */ 295 sigsep = ascii_strcmp (buf + buf_off, "-- ") == 0; 296 297 /* a fixed line either has no trailing space or is the 298 * signature separator */ 299 fixed = buf_len == buf_off || buf[buf_len - 1] != ' ' || sigsep; 300 301 /* print fixed-and-standalone, fixed-and-empty and sigsep lines as 302 * fixed lines */ 303 if ((fixed && (!fst.width || !buf_len)) || sigsep) 304 { 305 /* if we're within a flowed paragraph, terminate it */ 306 flush_par (s, &fst); 307 print_fixed_line (buf + buf_off, s, quotelevel, &fst); 308 continue; 309 } 310 311 /* for DelSp=yes, we need to strip one SP prior to CRLF on flowed lines */ 312 if (delsp && !fixed) 313 buf[--buf_len] = '\0'; 314 315 print_flowed_line (buf + buf_off, s, quotelevel, &fst, fixed); 316 } 317 318 flush_par (s, &fst); 319 320 FREE (&buf); 321 return (0); 322} 323 324/* 325 * This routine does RfC3676 space stuffing since it's a MUST. 326 * Space stuffing means that we have to add leading spaces to 327 * certain lines: 328 * - lines starting with a space 329 * - lines starting with 'From ' 330 * This routine is only called once right after editing the 331 * initial message so it's up to the user to take care of stuffing 332 * when editing the message several times before actually sending it 333 * 334 * This is more or less a hack as it replaces the message's content with 335 * a freshly created copy in a tempfile and modifies the file's mtime 336 * so we don't trigger code paths watching for mtime changes 337 */ 338void rfc3676_space_stuff (HEADER* hdr) 339{ 340#if DEBUG 341 int lc = 0; 342 size_t len = 0; 343 unsigned char c = '\0'; 344#endif 345 FILE *in = NULL, *out = NULL; 346 char buf[LONG_STRING]; 347 char tmpfile[_POSIX_PATH_MAX]; 348 349 if (!hdr || !hdr->content || !hdr->content->filename) 350 return; 351 352 dprint (2, (debugfile, "f=f: postprocess %s\n", hdr->content->filename)); 353 354 if ((in = safe_fopen (hdr->content->filename, "r")) == NULL) 355 return; 356 357 mutt_mktemp (tmpfile, sizeof (tmpfile)); 358 if ((out = safe_fopen (tmpfile, "w+")) == NULL) 359 { 360 safe_fclose (&in); 361 return; 362 } 363 364 while (fgets (buf, sizeof (buf), in)) 365 { 366 if (ascii_strncmp ("From ", buf, 5) == 0 || buf[0] == ' ') { 367 fputc (' ', out); 368#if DEBUG 369 lc++; 370 len = mutt_strlen (buf); 371 if (len > 0) 372 { 373 c = buf[len-1]; 374 buf[len-1] = '\0'; 375 } 376 dprint (4, (debugfile, "f=f: line %d needs space-stuffing: '%s'\n", 377 lc, buf)); 378 if (len > 0) 379 buf[len-1] = c; 380#endif 381 } 382 fputs (buf, out); 383 } 384 safe_fclose (&in); 385 safe_fclose (&out); 386 mutt_set_mtime (hdr->content->filename, tmpfile); 387 unlink (hdr->content->filename); 388 mutt_str_replace (&hdr->content->filename, tmpfile); 389}