1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2020 The Claws Mail Team and Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 %{
21 
22 #include "defs.h"
23 
24 #include <glib.h>
25 #include <glib/gi18n.h>
26 
27 #include <ctype.h>
28 
29 #include "procmsg.h"
30 #include "procmime.h"
31 #include "utils.h"
32 #include "codeconv.h"
33 #include "procheader.h"
34 #include "addr_compl.h"
35 #include "gtk/inputdialog.h"
36 
37 #include "quote_fmt.h"
38 #include "quote_fmt_lex.h"
39 #include "account.h"
40 #include "file-utils.h"
41 
42 /* decl */
43 /*
44 flex quote_fmt.l
45 bison -p quote_fmt quote_fmt.y
46 */
47 
48 int yylex(void);
49 
50 static MsgInfo *msginfo = NULL;
51 static PrefsAccount *account = NULL;
52 #ifdef USE_ENCHANT
53 static gchar default_dictionary[BUFFSIZE];
54 #endif
55 static gboolean *visible = NULL;
56 static gboolean dry_run = FALSE;
57 static gint maxsize = 0;
58 static gint stacksize = 0;
59 static GHashTable *var_table = NULL;
60 static GList *attachments = NULL;
61 
62 typedef struct st_buffer
63 {
64 	gchar *buffer;
65 	gint bufsize;
66 	gint bufmax;
67 } st_buffer;
68 
69 static struct st_buffer main_expr = { NULL, 0, 0 };
70 static struct st_buffer sub_expr = { NULL, 0, 0 };
71 static struct st_buffer* current = NULL;
72 
73 static const gchar *quote_str = NULL;
74 static const gchar *body = NULL;
75 static gint error = 0;
76 
77 static gint cursor_pos = -1;
78 
79 extern int quote_fmt_firsttime;
80 extern int line;
81 extern int escaped_string;
82 
add_visibility(gboolean val)83 static void add_visibility(gboolean val)
84 {
85 	stacksize++;
86 	if (maxsize < stacksize) {
87 		maxsize += 128;
88 		visible = g_realloc(visible, maxsize * sizeof(gboolean));
89 		if (visible == NULL)
90 			maxsize = 0;
91 	}
92 	if (visible != NULL)
93 		visible[stacksize - 1] = val;
94 }
95 
remove_visibility(void)96 static void remove_visibility(void)
97 {
98 	stacksize--;
99 	if (stacksize < 0) {
100 		g_warning("Error: visibility stack underflow");
101 		stacksize = 0;
102 	}
103 }
104 
add_buffer(const gchar * s)105 static void add_buffer(const gchar *s)
106 {
107 	gint len;
108 
109 	if (s == NULL)
110 		return;
111 
112 	len = strlen(s);
113 	if (current->bufsize + len + 1 > current->bufmax) {
114 		if (current->bufmax == 0)
115 			current->bufmax = 128;
116 		while (current->bufsize + len + 1 > current->bufmax)
117 			current->bufmax *= 2;
118 		current->buffer = g_realloc(current->buffer, current->bufmax);
119 	}
120 	strcpy(current->buffer + current->bufsize, s);
121 	current->bufsize += len;
122 }
123 
clear_buffer(void)124 static void clear_buffer(void)
125 {
126 	if (current->buffer)
127 		*current->buffer = '\0';
128 	else
129 		/* force to an empty string, as buffer should not be left unallocated */
130 		add_buffer("");
131 	current->bufsize = 0;
132 }
133 
quote_fmt_get_buffer(void)134 gchar *quote_fmt_get_buffer(void)
135 {
136 	if (current != &main_expr)
137 		g_warning("Error: parser still in sub-expr mode");
138 
139 	if (error != 0)
140 		return NULL;
141 	else
142 		return current->buffer;
143 }
144 
quote_fmt_get_attachments_list(void)145 GList *quote_fmt_get_attachments_list(void)
146 {
147 	return attachments;
148 }
149 
quote_fmt_get_line(void)150 gint quote_fmt_get_line(void)
151 {
152 	return line;
153 }
154 
quote_fmt_get_cursor_pos(void)155 gint quote_fmt_get_cursor_pos(void)
156 {
157 	return cursor_pos;
158 }
159 
160 #define INSERT(buf) \
161 	if (stacksize != 0 && visible[stacksize - 1])\
162 		add_buffer(buf); \
163 
164 #define INSERT_CHARACTER(chr) \
165 	if (stacksize != 0 && visible[stacksize - 1]) { \
166 		gchar tmp[2]; \
167 		tmp[0] = (chr); \
168 		tmp[1] = '\0'; \
169 		add_buffer(tmp); \
170 	}
171 
quote_fmt_reset_vartable(void)172 void quote_fmt_reset_vartable(void)
173 {
174 	if (var_table) {
175 		g_hash_table_destroy(var_table);
176 		var_table = NULL;
177 	}
178 	if (attachments) {
179 		GList *cur = attachments;
180 		while (cur) {
181 			g_free(cur->data);
182 			cur = g_list_next(cur);
183 		}
184 		g_list_free(attachments);
185 		attachments = NULL;
186 	}
187 }
188 
189 #ifdef USE_ENCHANT
quote_fmt_init(MsgInfo * info,const gchar * my_quote_str,const gchar * my_body,gboolean my_dry_run,PrefsAccount * compose_account,gboolean string_is_escaped,GtkAspell * compose_gtkaspell)190 void quote_fmt_init(MsgInfo *info, const gchar *my_quote_str,
191 		    const gchar *my_body, gboolean my_dry_run,
192 			PrefsAccount *compose_account,
193 			gboolean string_is_escaped,
194 			GtkAspell *compose_gtkaspell)
195 #else
196 void quote_fmt_init(MsgInfo *info, const gchar *my_quote_str,
197 		    const gchar *my_body, gboolean my_dry_run,
198 			PrefsAccount *compose_account,
199 			gboolean string_is_escaped)
200 #endif
201 {
202 	quote_str = my_quote_str;
203 	body = my_body;
204 	msginfo = info;
205 	account = compose_account;
206 #ifdef USE_ENCHANT
207 	gchar *dict = gtkaspell_get_default_dictionary(compose_gtkaspell);
208 	if (dict)
209 		strncpy2(default_dictionary, dict, sizeof(default_dictionary));
210 	else
211 		*default_dictionary = '\0';
212 #endif
213 	dry_run = my_dry_run;
214 	stacksize = 0;
215 	add_visibility(TRUE);
216 	main_expr.bufmax = 0;
217 	sub_expr.bufmax = 0;
218 	current = &main_expr;
219 	clear_buffer();
220 	error = 0;
221 	line = 1;
222 	escaped_string = string_is_escaped;
223 
224 	if (!var_table)
225 		var_table = g_hash_table_new_full(g_str_hash, g_str_equal,
226 				g_free, g_free);
227 
228         /*
229          * force LEX initialization
230          */
231 	quote_fmt_firsttime = 1;
232 	cursor_pos = -1;
233 }
234 
quote_fmterror(char * str)235 void quote_fmterror(char *str)
236 {
237 	g_warning("Error: %s at line %d", str, line);
238 	error = 1;
239 }
240 
quote_fmtwrap(void)241 int quote_fmtwrap(void)
242 {
243 	return 1;
244 }
245 
isseparator(int ch)246 static int isseparator(int ch)
247 {
248 	return g_ascii_isspace(ch) || ch == '.' || ch == '-';
249 }
250 
251 /*
252  * Search for glibc extended strftime timezone specs within haystack.
253  * If not found NULL is returned and the integer pointed by tzspeclen is
254  * not changed.
255  * If found a pointer to the start of the specification within haystack
256  * is returned and the integer pointed by tzspeclen is set to the lenght
257  * of specification.
258  */
strtzspec(const char * haystack,int * tzspeclen)259 static const char* strtzspec(const char *haystack, int *tzspeclen)
260 {
261 	const char *p = NULL;
262 	const char *q = NULL;
263 	const char *r = NULL;
264 
265 	p = strstr(haystack, "%");
266 	while (p != NULL) {
267 		q = p + 1;
268 		if (!*q) return NULL;
269 		r = strchr("_-0^#", *q); /* skip flags */
270 		if (r != NULL) {
271 			++q;
272 			if (!*q) return NULL;
273 		}
274 		while (*q >= '0' && *q <= '9') ++q; /* skip width */
275 		if (!*q) return NULL;
276 		if (*q == 'z' || *q == 'Z') { /* numeric or name */
277 			*tzspeclen = 1 + (q - p);
278 			return p;
279 		}
280 		p = strstr(q, "%");
281 	}
282 	return NULL;
283 }
284 
quote_fmt_show_date(const MsgInfo * msginfo,const gchar * format)285 static void quote_fmt_show_date(const MsgInfo *msginfo, const gchar *format)
286 {
287 	char  result[100];
288 	char *rptr;
289 	char  zone[6];
290 	struct tm lt;
291 	const char *fptr;
292 	const char *zptr;
293 
294 	if (!msginfo->date)
295 		return;
296 
297 	/*
298 	 * ALF - GNU C's strftime() has a nice format specifier
299 	 * for time zone offset (%z). Non-standard however, so
300 	 * emulate it.
301 	 */
302 
303 #define RLEFT (sizeof result) - (rptr - result)
304 
305 	zone[0] = 0;
306 
307 	if (procheader_date_parse_to_tm(msginfo->date, &lt, zone)) {
308 		/*
309 		 * break up format string in tiny bits delimited by valid %z's and
310 		 * feed it to strftime(). don't forget that '%%z' mean literal '%z'.
311 		 */
312 		for (rptr = result, fptr = format; fptr && *fptr && rptr < &result[sizeof result - 1];) {
313 			int	    perc, zlen;
314 			const char *p;
315 			char	   *tmp;
316 
317 			if (NULL != (zptr = strtzspec(fptr, &zlen))) {
318 				/*
319 				 * count nr. of prepended percent chars
320 				 */
321 				for (perc = 0, p = zptr; p && p >= format && *p == '%'; p--, perc++)
322 					;
323 				/*
324 				 * feed to strftime()
325 				 */
326 				tmp = g_strndup(fptr, zptr - fptr + (perc % 2 ? 0 : zlen));
327 				if (tmp) {
328 					rptr += strftime(rptr, RLEFT, tmp, &lt);
329 					g_free(tmp);
330 				}
331 				/*
332 				 * append time zone offset
333 				 */
334 				if (zone[0] && perc % 2)
335 					rptr += g_snprintf(rptr, RLEFT, "%s", zone);
336 				fptr = zptr + zlen;
337 			} else {
338 				rptr += strftime(rptr, RLEFT, fptr, &lt);
339 				fptr  = NULL;
340 			}
341 		}
342 
343 		if (g_utf8_validate(result, -1, NULL)) {
344 			INSERT(result);
345 		} else {
346 			gchar *utf = conv_codeset_strdup(result,
347 				conv_get_locale_charset_str_no_utf8(),
348 				CS_INTERNAL);
349 			if (utf == NULL ||
350 			    !g_utf8_validate(utf, -1, NULL)) {
351 				g_free(utf);
352 				utf = g_malloc(strlen(result)*2+1);
353 				conv_localetodisp(utf,
354 					strlen(result)*2+1, result);
355 			}
356 			if (g_utf8_validate(utf, -1, NULL)) {
357 				INSERT(utf);
358 			}
359 			g_free(utf);
360 		}
361 	}
362 #undef RLEFT
363 }
364 
quote_fmt_show_first_name(const MsgInfo * msginfo)365 static void quote_fmt_show_first_name(const MsgInfo *msginfo)
366 {
367 	guchar *p;
368 	gchar *str;
369 
370 	if (!msginfo->fromname)
371 		return;
372 
373 	p = (guchar*)strchr(msginfo->fromname, ',');
374 	if (p != NULL) {
375 		/* fromname is like "Duck, Donald" */
376 		p++;
377 		while (*p && isspace(*p)) p++;
378 		str = alloca(strlen((char *)p) + 1);
379 		if (str != NULL) {
380 			strcpy(str, (char *)p);
381 			INSERT(str);
382 		}
383 	} else {
384 		/* fromname is like "Donald Duck" */
385 		str = alloca(strlen(msginfo->fromname) + 1);
386 		if (str != NULL) {
387 			strcpy(str, msginfo->fromname);
388 			p = (guchar *)str;
389 			while (*p && !isspace(*p)) p++;
390 			*p = '\0';
391 			INSERT(str);
392 		}
393 	}
394 }
395 
quote_fmt_show_last_name(const MsgInfo * msginfo)396 static void quote_fmt_show_last_name(const MsgInfo *msginfo)
397 {
398 	gchar *p;
399 	gchar *str;
400 
401 	/* This probably won't work together very well with Middle
402            names and the like - thth */
403 	if (!msginfo->fromname)
404 		return;
405 
406 	str = alloca(strlen(msginfo->fromname) + 1);
407 	if (str != NULL) {
408 		strcpy(str, msginfo->fromname);
409 		p = strchr(str, ',');
410 		if (p != NULL) {
411 			/* fromname is like "Duck, Donald" */
412 			*p = '\0';
413 			INSERT(str);
414 		} else {
415 			/* fromname is like "Donald Duck" */
416 			p = str;
417 			while (*p && !isspace(*p)) p++;
418 			if (*p) {
419 			    /* We found a space. Get first
420 			     none-space char and insert
421 			     rest of string from there. */
422 			    while (*p && isspace(*p)) p++;
423 			    if (*p) {
424 				INSERT(p);
425 			    } else {
426 				/* If there is no none-space
427 				 char, just insert whole
428 				 fromname. */
429 				INSERT(str);
430 			    }
431 			} else {
432 			    /* If there is no space, just
433 			     insert whole fromname. */
434 			    INSERT(str);
435 			}
436 		}
437 	}
438 }
439 
quote_fmt_show_sender_initial(const MsgInfo * msginfo)440 static void quote_fmt_show_sender_initial(const MsgInfo *msginfo)
441 {
442 #define MAX_SENDER_INITIAL 20
443 	gchar tmp[MAX_SENDER_INITIAL];
444 	guchar *p;
445 	gchar *cur;
446 	gint len = 0;
447 
448 	if (!msginfo->fromname)
449 		return;
450 
451 	p = (guchar *)msginfo->fromname;
452 	cur = tmp;
453 	while (*p) {
454 		if (*p && g_utf8_validate((gchar *)p, 1, NULL)) {
455 			*cur = toupper(*p);
456 				cur++;
457 			len++;
458 			if (len >= MAX_SENDER_INITIAL - 1)
459 				break;
460 		} else
461 			break;
462 		while (*p && !isseparator(*p)) p++;
463 		while (*p && isseparator(*p)) p++;
464 	}
465 	*cur = '\0';
466 	INSERT(tmp);
467 }
468 
quote_fmt_show_msg(MsgInfo * msginfo,const gchar * body,gboolean quoted,gboolean signature,const gchar * quote_str)469 static void quote_fmt_show_msg(MsgInfo *msginfo, const gchar *body,
470 			       gboolean quoted, gboolean signature,
471 			       const gchar *quote_str)
472 {
473 	gchar buf[BUFFSIZE];
474 	FILE *fp;
475 
476 	if (!(msginfo->folder || body))
477 		return;
478 
479 	if (body)
480 		fp = str_open_as_stream(body);
481 	else {
482 		if (MSG_IS_ENCRYPTED(msginfo->flags))
483 			fp = procmime_get_first_encrypted_text_content(msginfo);
484 		else
485 			fp = procmime_get_first_text_content(msginfo);
486 	}
487 
488 	if (fp == NULL)
489 		g_warning("Can't get text part");
490 	else {
491 		account_sigsep_matchlist_create();
492 		while (fgets(buf, sizeof(buf), fp) != NULL) {
493 			strcrchomp(buf);
494 
495 			if (!signature && account_sigsep_matchlist_nchar_found(buf, "%s\n"))
496 				break;
497 
498 			if (quoted && quote_str)
499 				INSERT(quote_str);
500 
501 			INSERT(buf);
502 		}
503 		account_sigsep_matchlist_delete();
504 		fclose(fp);
505 	}
506 }
507 
quote_fmt_insert_file(const gchar * filename)508 static void quote_fmt_insert_file(const gchar *filename)
509 {
510 	FILE *file;
511 	char buffer[PATH_MAX];
512 
513 	if ((file = g_fopen(filename, "rb")) != NULL) {
514 		while (fgets(buffer, sizeof(buffer), file)) {
515 			INSERT(buffer);
516 		}
517 		fclose(file);
518 	}
519 
520 }
521 
quote_fmt_insert_program_output(const gchar * progname)522 static void quote_fmt_insert_program_output(const gchar *progname)
523 {
524 	FILE *file;
525 	char buffer[BUFFSIZE];
526 
527 	if ((file = get_command_output_stream(progname)) != NULL) {
528 		while (fgets(buffer, sizeof(buffer), file)) {
529 			INSERT(buffer);
530 		}
531 		fclose(file);
532 	}
533 }
534 
quote_fmt_insert_user_input(const gchar * varname)535 static void quote_fmt_insert_user_input(const gchar *varname)
536 {
537     gchar *buf = NULL;
538     gchar *text = NULL;
539 
540     if (dry_run)
541 	    return;
542 
543     if ((text = g_hash_table_lookup(var_table, varname)) == NULL) {
544 	    buf = g_strdup_printf(_("Enter text to replace '%s'"), varname);
545 	    text = input_dialog(_("Enter variable"), buf, "");
546 	    g_free(buf);
547 	    if (!text)
548 		    return;
549 	    g_hash_table_insert(var_table, g_strdup(varname), g_strdup(text));
550     } else {
551 	    /* don't free the one in hashtable at the end */
552 	    text = g_strdup(text);
553     }
554 
555     if (!text)
556 	    return;
557     INSERT(text);
558     g_free(text);
559 }
560 
quote_fmt_attach_file(const gchar * filename)561 static void quote_fmt_attach_file(const gchar *filename)
562 {
563 	attachments = g_list_append(attachments, g_strdup(filename));
564 }
565 
quote_fmt_attach_file_program_output(const gchar * progname)566 static void quote_fmt_attach_file_program_output(const gchar *progname)
567 {
568 	FILE *file;
569 	char buffer[BUFFSIZE];
570 
571 	if ((file = get_command_output_stream(progname)) != NULL) {
572 		/* get first line only */
573 		if (fgets(buffer, sizeof(buffer), file)) {
574 			/* trim trailing CR/LF */
575 			strretchomp(buffer);
576 			attachments = g_list_append(attachments, g_strdup(buffer));
577 		}
578 		fclose(file);
579 	}
580 }
581 
quote_fmt_complete_address(const gchar * addr)582 static gchar *quote_fmt_complete_address(const gchar *addr)
583 {
584 	gint count;
585 	gchar *res, *tmp, *email_addr;
586 	gchar **split;
587 
588 	debug_print("quote_fmt_complete_address: %s\n", addr);
589 	if (addr == NULL)
590 		return NULL;
591 
592 	/* if addr is a list of message, try the 1st element only */
593 	split = g_strsplit(addr, ",", -1);
594 	if (!split || !split[0] || *split[0] == '\0') {
595 		g_strfreev(split);
596 		return NULL;
597 	}
598 
599 	Xstrdup_a(email_addr, split[0], return NULL);
600 	extract_address(email_addr);
601 	if (!*email_addr) {
602 		g_strfreev(split);
603 		return NULL;
604 	}
605 
606 	res = NULL;
607 	start_address_completion(NULL);
608 	if (1 < (count = complete_address(email_addr))) {
609 		tmp = get_complete_address(1);
610 		res = procheader_get_fromname(tmp);
611 		g_free(tmp);
612 	}
613 	end_address_completion();
614 	g_strfreev(split);
615 
616 	debug_print("quote_fmt_complete_address: matched %s\n", res);
617 	return res;
618 }
619 
620 %}
621 
622 %union {
623 	char chr;
624 	char str[256];
625 }
626 
627 /* tokens SHOW */
628 %token SHOW_NEWSGROUPS
629 %token SHOW_DATE SHOW_FROM SHOW_FULLNAME SHOW_FIRST_NAME SHOW_LAST_NAME
630 %token SHOW_SENDER_INITIAL SHOW_SUBJECT SHOW_TO SHOW_MESSAGEID
631 %token SHOW_PERCENT SHOW_CC SHOW_REFERENCES SHOW_MESSAGE
632 %token SHOW_QUOTED_MESSAGE SHOW_BACKSLASH SHOW_TAB SHOW_MAIL_ADDRESS
633 %token SHOW_QUOTED_MESSAGE_NO_SIGNATURE SHOW_MESSAGE_NO_SIGNATURE
634 %token SHOW_EOL SHOW_QUESTION_MARK SHOW_EXCLAMATION_MARK SHOW_PIPE SHOW_OPARENT SHOW_CPARENT
635 %token SHOW_ACCOUNT_FULL_NAME SHOW_ACCOUNT_MAIL_ADDRESS SHOW_ACCOUNT_NAME SHOW_ACCOUNT_ORGANIZATION
636 %token SHOW_ACCOUNT_DICT SHOW_ACCOUNT_SIG SHOW_ACCOUNT_SIGPATH
637 %token SHOW_DICT SHOW_TAGS
638 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_CC
639 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_FROM
640 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_TO
641 /* tokens QUERY */
642 %token QUERY_DATE QUERY_FROM
643 %token QUERY_FULLNAME QUERY_SUBJECT QUERY_TO QUERY_NEWSGROUPS
644 %token QUERY_MESSAGEID QUERY_CC QUERY_REFERENCES
645 %token QUERY_ACCOUNT_FULL_NAME QUERY_ACCOUNT_ORGANIZATION QUERY_ACCOUNT_DICT
646 %token QUERY_ACCOUNT_SIG QUERY_ACCOUNT_SIGPATH
647 %token QUERY_DICT
648 %token QUERY_CC_FOUND_IN_ADDRESSBOOK
649 %token QUERY_FROM_FOUND_IN_ADDRESSBOOK
650 %token QUERY_TO_FOUND_IN_ADDRESSBOOK
651 /* tokens QUERY_NOT */
652 %token QUERY_NOT_DATE QUERY_NOT_FROM
653 %token QUERY_NOT_FULLNAME QUERY_NOT_SUBJECT QUERY_NOT_TO QUERY_NOT_NEWSGROUPS
654 %token QUERY_NOT_MESSAGEID QUERY_NOT_CC QUERY_NOT_REFERENCES
655 %token QUERY_NOT_ACCOUNT_FULL_NAME QUERY_NOT_ACCOUNT_ORGANIZATION QUERY_NOT_ACCOUNT_DICT
656 %token QUERY_NOT_ACCOUNT_SIG QUERY_NOT_ACCOUNT_SIGPATH
657 %token QUERY_NOT_DICT
658 %token QUERY_NOT_CC_FOUND_IN_ADDRESSBOOK
659 %token QUERY_NOT_FROM_FOUND_IN_ADDRESSBOOK
660 %token QUERY_NOT_TO_FOUND_IN_ADDRESSBOOK
661 /* other tokens */
662 %token INSERT_FILE INSERT_PROGRAMOUTPUT INSERT_USERINPUT
663 %token ATTACH_FILE ATTACH_PROGRAMOUTPUT
664 %token OPARENT CPARENT
665 %token CHARACTER
666 %token SHOW_DATE_EXPR
667 %token SET_CURSOR_POS
668 
669 %start quote_fmt
670 
671 %type <chr> CHARACTER
672 %type <chr> character
673 %type <str> string
674 
675 %%
676 
677 quote_fmt:
678 	character_or_special_or_insert_or_query_list ;
679 
680 sub_expr:
681 	character_or_special_list ;
682 
683 character_or_special_or_insert_or_query_list:
684 	character_or_special_or_insert_or_query character_or_special_or_insert_or_query_list
685 	| character_or_special_or_insert_or_query ;
686 
687 character_or_special_list:
688 	character_or_special character_or_special_list
689 	| character_or_special ;
690 
691 character_or_special_or_insert_or_query:
692 	character_or_special
693 	| query
694 	| query_not
695 	| insert
696 	| attach ;
697 
698 character_or_special:
699 	special
700 	| character
701 	{
702 		INSERT_CHARACTER($1);
703 	};
704 
705 character:
706 	CHARACTER
707 	;
708 
709 string:
710 	CHARACTER
711 	{
712 		$$[0] = $1;
713 		$$[1] = '\0';
714 	}
715 	| string CHARACTER
716 	{
717 		size_t len;
718 
719 		strncpy($$, $1, sizeof($$));
720 		$$[sizeof($$) - 1] = '\0';
721 		len = strlen($$);
722 		if (len + 1 < sizeof($$)) {
723 			$$[len + 1] = '\0';
724 			$$[len] = $2;
725 		}
726 	};
727 
728 special:
729 	SHOW_NEWSGROUPS
730 	{
731 		if (msginfo->newsgroups)
732 			INSERT(msginfo->newsgroups);
733 	}
734 	| SHOW_DATE_EXPR OPARENT string CPARENT
735 	{
736 		quote_fmt_show_date(msginfo, $3);
737 	}
738 	| SHOW_DATE
739 	{
740 		if (msginfo->date)
741 			INSERT(msginfo->date);
742 	}
743 	| SHOW_FROM
744 	{
745 		if (msginfo->from)
746 			INSERT(msginfo->from);
747 	}
748 	| SHOW_MAIL_ADDRESS
749 	{
750 		if (msginfo->from) {
751 			gchar *stripped_address = g_strdup(msginfo->from);
752 			extract_address(stripped_address);
753 			INSERT(stripped_address);
754 			g_free(stripped_address);
755 		}
756 	}
757 	| SHOW_FULLNAME
758 	{
759 		if (msginfo->fromname)
760 			INSERT(msginfo->fromname);
761 	}
762 	| SHOW_FIRST_NAME
763 	{
764 		quote_fmt_show_first_name(msginfo);
765 	}
766 	| SHOW_LAST_NAME
767     {
768 		quote_fmt_show_last_name(msginfo);
769 	}
770 	| SHOW_SENDER_INITIAL
771 	{
772 		quote_fmt_show_sender_initial(msginfo);
773 	}
774 	| SHOW_SUBJECT
775 	{
776 		if (msginfo->subject)
777 			INSERT(msginfo->subject);
778 	}
779 	| SHOW_TO
780 	{
781 		if (msginfo->to)
782 			INSERT(msginfo->to);
783 	}
784 	| SHOW_MESSAGEID
785 	{
786 		if (msginfo->msgid)
787 			INSERT(msginfo->msgid);
788 	}
789 	| SHOW_PERCENT
790 	{
791 		INSERT("%");
792 	}
793 	| SHOW_CC
794 	{
795 		if (msginfo->cc)
796 			INSERT(msginfo->cc);
797 	}
798 	| SHOW_REFERENCES
799 	{
800 		GSList *item;
801 
802 		INSERT(msginfo->inreplyto);
803 		for (item = msginfo->references; item != NULL; item = g_slist_next(item))
804 			if (item->data)
805 				INSERT(item->data);
806 	}
807 	| SHOW_MESSAGE
808 	{
809 		quote_fmt_show_msg(msginfo, body, FALSE, TRUE, quote_str);
810 	}
811 	| SHOW_QUOTED_MESSAGE
812 	{
813 		quote_fmt_show_msg(msginfo, body, TRUE, TRUE, quote_str);
814 	}
815 	| SHOW_MESSAGE_NO_SIGNATURE
816 	{
817 		quote_fmt_show_msg(msginfo, body, FALSE, FALSE, quote_str);
818 	}
819 	| SHOW_QUOTED_MESSAGE_NO_SIGNATURE
820 	{
821 		quote_fmt_show_msg(msginfo, body, TRUE, FALSE, quote_str);
822 	}
823 	| SHOW_ACCOUNT_FULL_NAME
824 	{
825 		if (account && account->name)
826 			INSERT(account->name);
827 	}
828 	| SHOW_ACCOUNT_MAIL_ADDRESS
829 	{
830 		if (account && account->address)
831 			INSERT(account->address);
832 	}
833 	| SHOW_ACCOUNT_NAME
834 	{
835 		if (account && account->account_name)
836 			INSERT(account->account_name);
837 	}
838 	| SHOW_ACCOUNT_ORGANIZATION
839 	{
840 		if (account && account->organization)
841 			INSERT(account->organization);
842 	}
843 	| SHOW_ACCOUNT_SIG
844 	{
845 		gchar *str = account_get_signature_str(account);
846 		INSERT(str);
847 		g_free(str);
848 	}
849 	| SHOW_ACCOUNT_SIGPATH
850 	{
851 		if (account && account->sig_path)
852 			INSERT(account->sig_path);
853 	}
854 	| SHOW_ACCOUNT_DICT
855 	{
856 #ifdef USE_ENCHANT
857 		if (account && account->enable_default_dictionary) {
858 			gchar *dictname = g_path_get_basename(account->default_dictionary);
859 			INSERT(dictname);
860 			g_free(dictname);
861 		}
862 #endif
863 	}
864 	| SHOW_DICT
865 	{
866 #ifdef USE_ENCHANT
867 		INSERT(default_dictionary);
868 #endif
869 	}
870 	| SHOW_TAGS
871 	{
872 		gchar *tags = procmsg_msginfo_get_tags_str(msginfo);
873 		if (tags) {
874 			INSERT(tags);
875 		}
876 		g_free(tags);
877 	}
878 	| SHOW_BACKSLASH
879 	{
880 		INSERT("\\");
881 	}
882 	| SHOW_TAB
883 	{
884 		INSERT("\t");
885 	}
886 	| SHOW_EOL
887 	{
888 		INSERT("\n");
889 	}
890 	| SHOW_QUESTION_MARK
891 	{
892 		INSERT("?");
893 	}
894 	| SHOW_EXCLAMATION_MARK
895 	{
896 		INSERT("!");
897 	}
898 	| SHOW_PIPE
899 	{
900 		INSERT("|");
901 	}
902 	| SHOW_OPARENT
903 	{
904 		INSERT("{");
905 	}
906 	| SHOW_CPARENT
907 	{
908 		INSERT("}");
909 	}
910 	| SET_CURSOR_POS
911 	{
912 		if (current->buffer)
913 			cursor_pos = g_utf8_strlen(current->buffer, -1);
914 		else
915 			cursor_pos = 0;
916 	}
917 	| SHOW_ADDRESSBOOK_COMPLETION_FOR_CC
918 	{
919 		gchar *tmp = quote_fmt_complete_address(msginfo->cc);
920 		if (tmp) {
921 			INSERT(tmp);
922 			g_free(tmp);
923 		}
924 	}
925 	| SHOW_ADDRESSBOOK_COMPLETION_FOR_FROM
926 	{
927 		gchar *tmp = quote_fmt_complete_address(msginfo->from);
928 		if (tmp) {
929 			INSERT(tmp);
930 			g_free(tmp);
931 		}
932 	}
933 	| SHOW_ADDRESSBOOK_COMPLETION_FOR_TO
934 	{
935 		gchar *tmp = quote_fmt_complete_address(msginfo->to);
936 		if (tmp) {
937 			INSERT(tmp);
938 			g_free(tmp);
939 		}
940 	};
941 
942 query:
943 	QUERY_DATE
944 	{
945 		add_visibility(msginfo->date != NULL);
946 	}
947 	OPARENT quote_fmt CPARENT
948 	{
949 		remove_visibility();
950 	}
951 	| QUERY_FROM
952 	{
953 		add_visibility(msginfo->from != NULL);
954 	}
955 	OPARENT quote_fmt CPARENT
956 	{
957 		remove_visibility();
958 	}
959 	| QUERY_FULLNAME
960 	{
961 		add_visibility(msginfo->fromname != NULL);
962 	}
963 	OPARENT quote_fmt CPARENT
964 	{
965 		remove_visibility();
966 	}
967 	| QUERY_SUBJECT
968 	{
969 		add_visibility(msginfo->subject != NULL);
970 	}
971 	OPARENT quote_fmt CPARENT
972 	{
973 		remove_visibility();
974 	}
975 	| QUERY_TO
976 	{
977 		add_visibility(msginfo->to != NULL);
978 	}
979 	OPARENT quote_fmt CPARENT
980 	{
981 		remove_visibility();
982 	}
983 	| QUERY_NEWSGROUPS
984 	{
985 		add_visibility(msginfo->newsgroups != NULL);
986 	}
987 	OPARENT quote_fmt CPARENT
988 	{
989 		remove_visibility();
990 	}
991 	| QUERY_MESSAGEID
992 	{
993 		add_visibility(msginfo->msgid != NULL);
994 	}
995 	OPARENT quote_fmt CPARENT
996 	{
997 		remove_visibility();
998 	}
999 	| QUERY_CC
1000 	{
1001 		add_visibility(msginfo->cc != NULL);
1002 	}
1003 	OPARENT quote_fmt CPARENT
1004 	{
1005 		remove_visibility();
1006 	}
1007 	| QUERY_REFERENCES
1008 	{
1009 		gboolean found;
1010 		GSList *item;
1011 
1012 		found = (msginfo->inreplyto != NULL);
1013 		for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
1014 			if (item->data)
1015 				found = TRUE;
1016 		add_visibility(found == TRUE);
1017 	}
1018 	OPARENT quote_fmt CPARENT
1019 	{
1020 		remove_visibility();
1021 	}
1022 	| QUERY_ACCOUNT_FULL_NAME
1023 	{
1024 		add_visibility(account != NULL && account->name != NULL);
1025 	}
1026 	OPARENT quote_fmt CPARENT
1027 	{
1028 		remove_visibility();
1029 	}
1030 	| QUERY_ACCOUNT_ORGANIZATION
1031 	{
1032 		add_visibility(account != NULL && account->organization != NULL);
1033 	}
1034 	OPARENT quote_fmt CPARENT
1035 	{
1036 		remove_visibility();
1037 	}
1038 	| QUERY_ACCOUNT_SIG
1039 	{
1040 		gchar *str = account_get_signature_str(account);
1041 		add_visibility(str != NULL && * str != '\0');
1042 		g_free(str);
1043 	}
1044 	OPARENT quote_fmt CPARENT
1045 	{
1046 		remove_visibility();
1047 	}
1048 	| QUERY_ACCOUNT_SIGPATH
1049 	{
1050 		add_visibility(account != NULL && account->sig_path != NULL
1051 				&& *account->sig_path != '\0');
1052 	}
1053 	OPARENT quote_fmt CPARENT
1054 	{
1055 		remove_visibility();
1056 	}
1057 	| QUERY_ACCOUNT_DICT
1058 	{
1059 #ifdef USE_ENCHANT
1060 		add_visibility(account != NULL && account->enable_default_dictionary == TRUE &&
1061 				account->default_dictionary != NULL && *account->default_dictionary != '\0');
1062 #else
1063 		add_visibility(FALSE);
1064 #endif
1065 	}
1066 	OPARENT quote_fmt CPARENT
1067 	{
1068 		remove_visibility();
1069 	}
1070 	| QUERY_DICT
1071 	{
1072 #ifdef USE_ENCHANT
1073 		add_visibility(*default_dictionary != '\0');
1074 #else
1075 		add_visibility(FALSE);
1076 #endif
1077 	}
1078 	OPARENT quote_fmt CPARENT
1079 	{
1080 		remove_visibility();
1081 	}
1082 	| QUERY_CC_FOUND_IN_ADDRESSBOOK
1083 	{
1084 		gchar *tmp = quote_fmt_complete_address(msginfo->cc);
1085 		add_visibility(tmp != NULL && *tmp != '\0');
1086 		g_free(tmp);
1087 	}
1088 	OPARENT quote_fmt CPARENT
1089 	{
1090 		remove_visibility();
1091 	}
1092 	| QUERY_FROM_FOUND_IN_ADDRESSBOOK
1093 	{
1094 		gchar *tmp = quote_fmt_complete_address(msginfo->from);
1095 		add_visibility(tmp != NULL && *tmp != '\0');
1096 		g_free(tmp);
1097 	}
1098 	OPARENT quote_fmt CPARENT
1099 	{
1100 		remove_visibility();
1101 	}
1102 	| QUERY_TO_FOUND_IN_ADDRESSBOOK
1103 	{
1104 		gchar *tmp = quote_fmt_complete_address(msginfo->to);
1105 		add_visibility(tmp != NULL && *tmp != '\0');
1106 		g_free(tmp);
1107 	}
1108 	OPARENT quote_fmt CPARENT
1109 	{
1110 		remove_visibility();
1111 	};
1112 
1113 query_not:
1114 	QUERY_NOT_DATE
1115 	{
1116 		add_visibility(msginfo->date == NULL);
1117 	}
1118 	OPARENT quote_fmt CPARENT
1119 	{
1120 		remove_visibility();
1121 	}
1122 	| QUERY_NOT_FROM
1123 	{
1124 		add_visibility(msginfo->from == NULL);
1125 	}
1126 	OPARENT quote_fmt CPARENT
1127 	{
1128 		remove_visibility();
1129 	}
1130 	| QUERY_NOT_FULLNAME
1131 	{
1132 		add_visibility(msginfo->fromname == NULL);
1133 	}
1134 	OPARENT quote_fmt CPARENT
1135 	{
1136 		remove_visibility();
1137 	}
1138 	| QUERY_NOT_SUBJECT
1139 	{
1140 		add_visibility(msginfo->subject == NULL);
1141 	}
1142 	OPARENT quote_fmt CPARENT
1143 	{
1144 		remove_visibility();
1145 	}
1146 	| QUERY_NOT_TO
1147 	{
1148 		add_visibility(msginfo->to == NULL);
1149 	}
1150 	OPARENT quote_fmt CPARENT
1151 	{
1152 		remove_visibility();
1153 	}
1154 	| QUERY_NOT_NEWSGROUPS
1155 	{
1156 		add_visibility(msginfo->newsgroups == NULL);
1157 	}
1158 	OPARENT quote_fmt CPARENT
1159 	{
1160 		remove_visibility();
1161 	}
1162 	| QUERY_NOT_MESSAGEID
1163 	{
1164 		add_visibility(msginfo->msgid == NULL);
1165 	}
1166 	OPARENT quote_fmt CPARENT
1167 	{
1168 		remove_visibility();
1169 	}
1170 	| QUERY_NOT_CC
1171 	{
1172 		add_visibility(msginfo->cc == NULL);
1173 	}
1174 	OPARENT quote_fmt CPARENT
1175 	{
1176 		remove_visibility();
1177 	}
1178 	| QUERY_NOT_REFERENCES
1179 	{
1180 		gboolean found;
1181 		GSList *item;
1182 
1183 		found = (msginfo->inreplyto != NULL);
1184 		for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
1185 			if (item->data)
1186 				found = TRUE;
1187 		add_visibility(found == FALSE);
1188 	}
1189 	OPARENT quote_fmt CPARENT
1190 	{
1191 		remove_visibility();
1192 	}
1193 	| QUERY_NOT_ACCOUNT_FULL_NAME
1194 	{
1195 		add_visibility(account == NULL || account->name == NULL);
1196 	}
1197 	OPARENT quote_fmt CPARENT
1198 	{
1199 		remove_visibility();
1200 	}
1201 	| QUERY_NOT_ACCOUNT_ORGANIZATION
1202 	{
1203 		add_visibility(account == NULL || account->organization == NULL);
1204 	}
1205 	OPARENT quote_fmt CPARENT
1206 	{
1207 		remove_visibility();
1208 	}
1209 	| QUERY_NOT_ACCOUNT_SIG
1210 	{
1211 		gchar *str = account_get_signature_str(account);
1212 		add_visibility(str == NULL || *str == '\0');
1213 		g_free(str);
1214 	}
1215 	OPARENT quote_fmt CPARENT
1216 	{
1217 		remove_visibility();
1218 	}
1219 	| QUERY_NOT_ACCOUNT_SIGPATH
1220 	{
1221 		add_visibility(account == NULL || account->sig_path == NULL
1222 				|| *account->sig_path == '\0');
1223 	}
1224 	OPARENT quote_fmt CPARENT
1225 	{
1226 		remove_visibility();
1227 	}
1228 	| QUERY_NOT_ACCOUNT_DICT
1229 	{
1230 #ifdef USE_ENCHANT
1231 		add_visibility(account == NULL || account->enable_default_dictionary == FALSE
1232 				|| *account->default_dictionary == '\0');
1233 #else
1234 		add_visibility(FALSE);
1235 #endif
1236 	}
1237 	OPARENT quote_fmt CPARENT
1238 	{
1239 		remove_visibility();
1240 	}
1241 	| QUERY_NOT_DICT
1242 	{
1243 #ifdef USE_ENCHANT
1244 		add_visibility(*default_dictionary == '\0');
1245 #else
1246 		add_visibility(FALSE);
1247 #endif
1248 	}
1249 	OPARENT quote_fmt CPARENT
1250 	{
1251 		remove_visibility();
1252 	}
1253 	| QUERY_NOT_CC_FOUND_IN_ADDRESSBOOK
1254 	{
1255 		gchar *tmp = quote_fmt_complete_address(msginfo->cc);
1256 		add_visibility(tmp == NULL || *tmp == '\0');
1257 		g_free(tmp);
1258 	}
1259 	OPARENT quote_fmt CPARENT
1260 	{
1261 		remove_visibility();
1262 	}
1263 	| QUERY_NOT_FROM_FOUND_IN_ADDRESSBOOK
1264 	{
1265 		gchar *tmp = quote_fmt_complete_address(msginfo->from);
1266 		add_visibility(tmp == NULL || *tmp == '\0');
1267 		g_free(tmp);
1268 	}
1269 	OPARENT quote_fmt CPARENT
1270 	{
1271 		remove_visibility();
1272 	}
1273 	| QUERY_NOT_TO_FOUND_IN_ADDRESSBOOK
1274 	{
1275 		gchar *tmp = quote_fmt_complete_address(msginfo->to);
1276 		add_visibility(tmp == NULL || *tmp == '\0');
1277 		g_free(tmp);
1278 	}
1279 	OPARENT quote_fmt CPARENT
1280 	{
1281 		remove_visibility();
1282 	};
1283 
1284 insert:
1285 	INSERT_FILE
1286 	{
1287 		current = &sub_expr;
1288 		clear_buffer();
1289 	}
1290 	OPARENT sub_expr CPARENT
1291 	{
1292 		current = &main_expr;
1293 		if (!dry_run) {
1294 			quote_fmt_insert_file(sub_expr.buffer);
1295 		}
1296 	}
1297 	| INSERT_PROGRAMOUTPUT
1298 	{
1299 		current = &sub_expr;
1300 		clear_buffer();
1301 	}
1302 	OPARENT sub_expr CPARENT
1303 	{
1304 		current = &main_expr;
1305 		if (!dry_run) {
1306 			quote_fmt_insert_program_output(sub_expr.buffer);
1307 		}
1308 	}
1309 	| INSERT_USERINPUT
1310 	{
1311 		current = &sub_expr;
1312 		clear_buffer();
1313 	}
1314 	OPARENT sub_expr CPARENT
1315 	{
1316 		current = &main_expr;
1317 		if (!dry_run) {
1318 			quote_fmt_insert_user_input(sub_expr.buffer);
1319 		}
1320 	};
1321 
1322 attach:
1323 	ATTACH_FILE
1324 	{
1325 		current = &sub_expr;
1326 		clear_buffer();
1327 	}
1328 	OPARENT sub_expr CPARENT
1329 	{
1330 		current = &main_expr;
1331 		if (!dry_run) {
1332 			quote_fmt_attach_file(sub_expr.buffer);
1333 		}
1334 	}
1335 	| ATTACH_PROGRAMOUTPUT
1336 	{
1337 		current = &sub_expr;
1338 		clear_buffer();
1339 	}
1340 	OPARENT sub_expr CPARENT
1341 	{
1342 		current = &main_expr;
1343 		if (!dry_run) {
1344 			quote_fmt_attach_file_program_output(sub_expr.buffer);
1345 		}
1346 	};
1347 ;
1348