* Fix not allow remote execution by adding PIPE_NOSHELL to the opening of a url by
[alpine.git] / pith / osdep / color.c
blobcad5502e211d07a53e527717bc130e24259cf6aa
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: color.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006 University of Washington
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * ========================================================================
20 * These routines themselves aren't necessarily OS-specific, they
21 * are all called from within pico, pine and webpine.
23 * They used to be in pico source (osdep/unix, mswin.c), but considering
24 * webpine uses color as well and it should *not* have to be linked
25 * against libpico and considering pico uses these routines but should
26 * not have to link against libpith (and in turn c-client) we put them
27 * in pith/osdep which should only have to link against system libraries
28 * and thus be include freely in all of pine, pico and webpine.
29 */
32 #include <system.h>
33 #include "./color.h"
34 #include "./collate.h"
38 * new_color_pair - allocate a new color pair structure assigning
39 * given foreground and background color strings
41 COLOR_PAIR *
42 new_color_pair(char *fg, char *bg)
44 COLOR_PAIR *ret;
46 if((ret = (COLOR_PAIR *) malloc(sizeof(*ret))) != NULL){
47 memset(ret, 0, sizeof(*ret));
48 if(fg){
49 strncpy(ret->fg, fg, MAXCOLORLEN);
50 ret->fg[MAXCOLORLEN] = '\0';
53 if(bg){
54 strncpy(ret->bg, bg, MAXCOLORLEN);
55 ret->bg[MAXCOLORLEN] = '\0';
59 return(ret);
64 * free_color_pair - release resources associated with given
65 * color pair structure
67 void
68 free_color_pair(COLOR_PAIR **cp)
70 if(cp && *cp){
71 free(*cp);
72 *cp = NULL;
78 * Just like pico_set_color except it doesn't set the color, it just
79 * returns the value. Assumes def of PSC_NONE, since otherwise we always
80 * succeed and don't need to call this.
82 int
83 pico_is_good_colorpair(COLOR_PAIR *cp)
85 return(cp && pico_is_good_color(cp->fg) && pico_is_good_color(cp->bg));
89 COLOR_PAIR *
90 pico_set_colorp(COLOR_PAIR *col, int flags)
92 return(pico_set_colors(col ? col->fg : NULL, col ? col->bg : NULL, flags));
96 /*
97 * Extended Justification support also does not belong here
98 * but otherwise webpine will not build, so we move everything
99 * here. Hopefully this will be the permanent place for these
100 * routines. These routines used to be in pico/word.c
102 #define NSTRING 256
103 #include "../../include/general.h"
105 /* Support of indentation of paragraphs */
106 #define is_indent_char(c) (((c) == '.' || (c) == '}' || (c) == RPAREN || \
107 (c) == '*' || (c) == '+' || is_a_digit(c) || \
108 ISspace(c) || (c) == '-' || \
109 (c) == ']') ? 1 : 0)
110 #define allowed_after_digit(c,word,k) ((((c) == '.' && \
111 allowed_after_period(next((word),(k)))) ||\
112 (c) == RPAREN || (c) == '}' || (c) == ']' ||\
113 ISspace(c) || is_a_digit(c) || \
114 ((c) == '-' ) && \
115 allowed_after_dash(next((word),(k)))) \
116 ? 1 : 0)
117 #define allowed_after_period(c) (((c) == RPAREN || (c) == '}' || (c) == ']' ||\
118 ISspace(c) || (c) == '-' || \
119 is_a_digit(c)) ? 1 : 0)
120 #define allowed_after_parenth(c) (ISspace(c) ? 1 : 0)
121 #define allowed_after_space(c) (ISspace(c) ? 1 : 0)
122 #define allowed_after_braces(c) (ISspace(c) ? 1 : 0)
123 #define allowed_after_star(c) ((ISspace(c) || (c) == RPAREN ||\
124 (c) == ']' || (c) == '}') ? 1 : 0)
125 #define allowed_after_dash(c) ((ISspace(c) || is_a_digit(c)) ? 1 : 0)
126 #define EOLchar(c) (((c) == '.' || (c) == ':' || (c) == '?' ||\
127 (c) == '!') ? 1 : 0)
130 /* Extended justification support */
131 #define is_cquote(c) ((c) == '>' || (c) == '|' || (c) == ']' || (c) == ':')
132 #define is_cword(c) ((((c) >= 'a') && ((c) <= 'z')) || \
133 (((c) >= 'A') && ((c) <= 'Z')) || \
134 (((c) >= '0') && ((c) <= '9')) || \
135 ((c) == ' ') || ((c) == '?') || \
136 ((c) == '@') || ((c) == '.') || \
137 ((c) == '!') || ((c) == '\'') || \
138 ((c) == ',') || ((c) == '\"') ? 1 : 0)
139 #define isaquote(c) ((c) == '\"' || (c) == '\'')
140 #define is8bit(c) ((((int) (c)) & 0x80) ? 1 : 0)
141 #define iscontrol(c) (iscntrl(((int) (c)) & 0x7f) ? 1 : 0)
142 #define forbidden(c) (((c) == '\"') || ((c) == '\'') || ((c) == '$') ||\
143 ((c) == ',') || ((c) == '.') || ((c) == '-') ||\
144 ((c) == LPAREN) || ((c) == '/')|| ((c) == '`') ||\
145 ((c) == '{') || ((c) == '\\') || (iscontrol((c))) ||\
146 (((c) >= '0') && ((c) <= '9')) || ((c) == '?'))
147 #define is_cletter(c) ((((c) >= 'a') && ((c) <= 'z'))) ||\
148 ((((c) >= 'A') && ((c) <= 'Z'))||\
149 is8bit(c))
150 #define is_cnumber(c) ((c) >= '0' && (c) <= '9')
151 #define allwd_after_word(c) (((c) == ' ') || ((c) == '>') || is_cletter(c))
152 #define allwd_after_qsword(c) (((c) != '\\') && ((c) != RPAREN))
153 #define before(word,i) (((i) > 0) ? (word)[(i) - 1] : 0)
154 #define next(w,i) ((((w)[(i)]) != 0) ? ((w)[(i) + 1]) : 0)
155 #define now(w,i) ((w)[(i)])
156 #define is_qsword(c) (((c) == ':') || ((c) == RPAREN) ? 1 : 0)
157 #define is_colon(c) (((c) == ':') ? 1 : 0)
158 #define is_rarrow(c) (((c) == '>') ? 1 : 0)
159 #define is_tilde(c) (((c) == '~') ? 1 : 0)
160 #define is_dash(c) (((c) == '-') ? 1 : 0)
161 #define is_pound(c) (((c) == '#') ? 1 : 0)
162 #define is_a_digit(c) ((((c) >= '0') && ((c) <= '9')) ? 1 : 0)
163 #define is_allowed(c) (is_cquote(c) || is_cword(c) || is_dash(c) || \
164 is_pound(c))
165 #define qs_allowed(a) (((a)->qstype != qsGdb) && ((a)->qstype != qsProg))
167 /* Internal justification functions */
168 QSTRING_S *is_quote(char **, char *, int);
169 QSTRING_S *qs_normal_part(QSTRING_S *);
170 QSTRING_S *qs_remove_trailing_spaces(QSTRING_S *);
171 QSTRING_S *trim_qs_from_cl(QSTRING_S *, QSTRING_S *, QSTRING_S *);
172 QSTRING_S *fix_qstring(QSTRING_S *, QSTRING_S *, QSTRING_S *);
173 QSTRING_S *fix_qstring_allowed(QSTRING_S *, QSTRING_S *, QSTRING_S *);
174 QSTRING_S *qs_add(char **, char *, QStrType, int, int, int, int);
175 QSTRING_S *remove_qsword(QSTRING_S *);
176 QSTRING_S *do_raw_quote_match(char **, char *, char *, char *, QSTRING_S **, QSTRING_S **);
177 void free_qs(QSTRING_S **);
178 int word_is_prog(char *);
179 int qstring_is_normal(QSTRING_S *);
180 int exists_good_part(QSTRING_S *);
181 int strcmp_qs(char *, char *);
182 int count_levels_qstring(QSTRING_S *);
183 int same_qstring(QSTRING_S *, QSTRING_S *);
184 int isaword(char *,int ,int);
185 int isamailbox(char *,int ,int);
186 int double_check_qstr(char *);
189 word_is_prog(char *word)
191 static char *list1[] = {"#include",
192 "#define",
193 "#ifdef",
194 "#ifndef",
195 "#elif",
196 "#if",
197 NULL};
198 static char *list2[] = {"#else",
199 "#endif",
200 NULL};
201 int i, j = strlen(word), k, rv = 0;
203 for(i = 0; rv == 0 && list1[i] && (k = strlen(list1[i])) && k < j; i++)
204 if(!strncmp(list1[i], word, k) && ISspace(word[k]))
205 rv++;
207 if(rv)
208 return rv;
210 for(i = 0; rv == 0 && list2[i] && (k = strlen(list2[i])) && k <= j; i++)
211 if(!strncmp(list2[i], word, k) && (!word[k] || ISspace(word[k])))
212 rv++;
214 return rv;
218 * This function creates a qstring pointer with the information that
219 * is_quote handles to it.
220 * Parameters:
221 * qs - User supplied quote string
222 * word - The line of text that the user is trying to read/justify
223 * beginw - Where we need to start copying from
224 * endw - Where we end copying
225 * offset - Any offset in endw that we need to account for
226 * typeqs - type of the string to be created
227 * neednext - boolean, indicating if we need to compute the next field
228 * of leave it NULL
230 * It is a mistake to call this function if beginw >= endw + offset.
231 * Please note the equality sign in the above inequality (this is because
232 * we always assume that qstring->value != "").
234 QSTRING_S *
235 qs_add(char **qs, char word[NSTRING], QStrType typeqs, int beginw, int endw,
236 int offset, int neednext)
238 QSTRING_S *qstring, *nextqs;
239 int i;
241 qstring = (QSTRING_S *) malloc (sizeof(QSTRING_S));
242 memset (qstring, 0, sizeof(QSTRING_S));
243 qstring->qstype = qsNormal;
245 if (beginw == 0){
246 beginw = endw + offset;
247 qstring->qstype = typeqs;
250 nextqs = neednext ? is_quote(qs, word+beginw, 1) : NULL;
252 qstring->value = (char *) malloc((beginw+1)*sizeof(char));
253 strncpy(qstring->value, word, beginw);
254 qstring->value[beginw] = '\0';
256 qstring->next = nextqs;
258 return qstring;
262 qstring_is_normal(QSTRING_S *cl)
264 for (;cl && (cl->qstype == qsNormal); cl = cl->next);
265 return cl ? 0 : 1;
269 * Given a quote string, this function returns the part that is the leading
270 * normal part of it. (the normal part is the part that is tagged qsNormal,
271 * that is to say, the one that is not controversial at all (like qsString
272 * for example).
274 QSTRING_S *
275 qs_normal_part(QSTRING_S *cl)
278 if (!cl) /* nothing in, nothing out */
279 return cl;
281 if (cl->qstype != qsNormal)
282 free_qs(&cl);
284 if (cl)
285 cl->next = qs_normal_part(cl->next);
287 return cl;
291 * this function removes trailing spaces from a quote string, but leaves the
292 * last one if there are trailing spaces
294 QSTRING_S *
295 qs_remove_trailing_spaces(QSTRING_S *cl)
297 QSTRING_S *rl = cl;
298 if (!cl) /* nothing in, nothing out */
299 return cl;
301 if (cl->next)
302 cl->next = qs_remove_trailing_spaces(cl->next);
303 else{
304 if (value_is_space(cl->value))
305 free_qs(&cl);
306 else{
307 int i, l;
308 i = l = strlen(cl->value) - 1;
309 while (cl->value && cl->value[i]
310 && ISspace(cl->value[i]))
311 i--;
312 i += (i < l) ? 2 : 1;
313 cl->value[i] = '\0';
316 return cl;
320 * This function returns if two strings are the same quote string.
321 * The call is not symmetric. cl must preceed the line nl. This function
322 * should be called for comparing the last part of cl and nl.
325 strcmp_qs(char *valuecl, char *valuenl)
327 int j;
329 for (j = 0; valuecl[j] && (valuecl[j] == valuenl[j]); j++);
330 return !strcmp(valuecl, valuenl)
331 || (valuenl[j] && value_is_space(valuenl+j)
332 && value_is_space(valuecl+j)
333 && strlenis(valuecl+j) >= strlenis(valuenl+j))
334 || (!valuenl[j] && value_is_space(valuecl+j));
338 count_levels_qstring(QSTRING_S *cl)
340 int count;
341 for (count = 0; cl ; count++, cl = cl->next);
343 return count;
347 value_is_space(char *value)
349 for (; value && *value && ISspace(*value); value++);
351 return value && *value ? 0 : 1;
354 void
355 free_qs(QSTRING_S **cl)
357 if (!(*cl))
358 return;
360 if ((*cl)->next)
361 free_qs(&((*cl)->next));
363 (*cl)->next = (QSTRING_S *) NULL;
365 if ((*cl)->value)
366 free((void *)(*cl)->value);
367 (*cl)->value = (char *) NULL;
368 free((void *)(*cl));
369 *cl = (QSTRING_S *) NULL;
373 * This function returns the number of agreements between
374 * cl and nl. The call is not symmetric. cl must be the line
375 * preceding nl.
378 same_qstring(QSTRING_S *cl, QSTRING_S *nl)
380 int same = 0, done = 0;
382 for (;cl && nl && !done; cl = cl->next, nl = nl->next)
383 if (cl->qstype == nl->qstype
384 && (!strcmp(cl->value, nl->value)
385 || (!cl->next && strcmp_qs(cl->value, nl->value))))
386 same++;
387 else
388 done++;
389 return same;
392 QSTRING_S *
393 trim_qs_from_cl(QSTRING_S *cl, QSTRING_S *nl, QSTRING_S *pl)
395 QSTRING_S *cqstring = pl ? pl : nl;
396 QSTRING_S *tl = pl ? pl : nl;
397 int p, c;
399 if (qstring_is_normal(tl))
400 return tl;
402 p = same_qstring(pl ? pl : cl, pl ? cl : nl);
404 for (c = 1; c < p; c++, cl = cl->next, tl = tl->next);
407 * cl->next and tl->next differ, it may be because cl->next does not
408 * exist or tl->next does not exist or simply both exist but are
409 * different. In this last case, it may be that cl->next->value is made
410 * of spaces. If this is the case, tl advances once more.
413 if (tl->next){
414 if (cl && cl->next && value_is_space(cl->next->value))
415 tl = tl->next;
416 if (tl->next)
417 free_qs(&(tl->next));
420 if (!p)
421 free_qs(&cqstring);
423 return cqstring;
426 /* This function trims cl so that it returns a real quote string based
427 * on information gathered from the previous and next lines. pl and cl are
428 * also trimmed, but that is done in another function, not here.
430 QSTRING_S *
431 fix_qstring(QSTRING_S *cl, QSTRING_S *nl, QSTRING_S *pl)
433 QSTRING_S *cqstring = cl, *nqstring = nl, *pqstring = pl;
434 int c, n;
436 if (qstring_is_normal(cl))
437 return cl;
439 c = count_levels_qstring(cl);
440 n = same_qstring(cl,nl);
442 if (!n){ /* no next line or no agreement with next line */
443 int p = same_qstring(pl, cl); /* number of agreements between pl and cl */
444 QSTRING_S *tl; /* test line */
447 * Here p <= c, so either p < c or p == c. If p == c, we are done,
448 * and return cl. If not, there are two cases, either p == 0 or
449 * 0 < p < c. In the first case, we do not have enough evidence
450 * to return anything other than the normal part of cl, in the second
451 * case we can only return p levels of cl.
454 if (p == c)
455 tl = cqstring;
456 else{
457 if (p){
458 for (c = 1; c < p; c++)
459 cl = cl->next;
460 free_qs(&(cl->next));
461 tl = cqstring;
463 else{
464 int done = 0;
465 QSTRING_S *al = cl; /* another line */
467 * Ok, we really don't have enough evidence to return anything,
468 * different from the normal part of cl, but it could be possible
469 * that we may want to accept the not-normal part, so we better
470 * make an extra test to determine what needs to be freed
472 while (pl && cl && cl->qstype == pl->qstype
473 && !strucmp(cl->value, pl->value)){
474 cl = cl->next;
475 pl = pl->next;
477 if (pl && cl && cl->qstype == pl->qstype
478 && strcmp_qs(pl->value, cl->value))
479 cl = cl->next; /* next level differs only in spaces */
480 while (!done){
481 while (cl && cl->qstype == qsNormal)
482 cl = cl->next;
483 if (cl){
484 if ((cl->qstype == qsString)
485 && (cl->value[strlen(cl->value) - 1] == '>'))
486 cl = cl->next;
487 else done++;
489 else done++;
491 if (al == cl){
492 free_qs(&(cl));
493 tl = cl;
495 else {
496 while (al && (al->next != cl))
497 al = al->next;
498 cl = al;
499 if (cl && cl->next)
500 free_qs(&(cl->next));
501 tl = cqstring;
505 return tl;
507 if (n + 1 < c){ /* if there are not enough agreements */
508 int p = same_qstring(pl, cl); /* number of agreement between pl and cl */
509 QSTRING_S *tl; /* test line */
511 * There's no way we can use cl in this case, but we can use
512 * part of cl, this is if pl does not have more agreements
513 * with cl.
515 if (p == c)
516 tl = cqstring;
517 else{
518 int m = p < n ? n : p;
519 for (c = 1; c < m; c++){
520 pl = pl ? pl->next : (QSTRING_S *) NULL;
521 nl = nl ? nl->next : (QSTRING_S *) NULL;
522 cl = cl->next;
524 if (p == n && pl && pl->next && nl && nl->next
525 && ((cl->next->qstype == pl->next->qstype)
526 || (cl->next->qstype == nl->next->qstype))
527 && (strcmp_qs(cl->next->value, pl->next->value)
528 || strcmp_qs(pl->next->value, cl->next->value)
529 || strcmp_qs(cl->next->value, nl->next->value)
530 || strcmp_qs(nl->next->value, cl->next->value)))
531 cl = cl->next; /* next level differs only in spaces */
532 if (cl->next)
533 free_qs(&(cl->next));
534 tl = cqstring;
536 return tl;
538 if (n + 1 == c){
539 int p = same_qstring(pl, cl);
540 QSTRING_S *tl; /* test line */
543 * p <= c, so p <= n+1, which means p < n + 1 or p == n + 1.
544 * If p < n + 1, then p <= n.
545 * so we have three possibilities:
546 * p == n + 1 or p == n or p < n.
547 * In the first case we copy p == n + 1 == c levels, in the second
548 * and third case we copy n levels, and check if we can copy the
549 * n + 1 == c level.
551 if (p == n + 1) /* p == c, in the above sense of c */
552 tl = cl; /* use cl, this is enough evidence */
553 else{
554 for (c = 1; c < n; c++)
555 cl = cl->next;
557 * Here c == n, we only have one more level of cl, and at least one
558 * more level of nl
560 if (cl->next->qstype == qsNormal)
561 cl = cl->next;
562 if (cl->next)
563 free_qs(&(cl->next));
564 tl = cqstring;
566 return tl;
568 if (n == c) /* Yeah!!! */
569 return cqstring;
572 QSTRING_S *
573 fix_qstring_allowed(QSTRING_S *cl, QSTRING_S *nl, QSTRING_S *pl)
575 if(!cl)
576 return (QSTRING_S *) NULL;
578 if (qs_allowed(cl))
579 cl->next = fix_qstring_allowed(cl->next, (nl ? nl->next : NULL),
580 (pl ? pl->next : NULL));
581 else
582 if((nl && cl->qstype == nl->qstype) || (pl && cl->qstype == pl->qstype)
583 || (!nl && !pl))
584 free_qs(&cl);
585 return cl;
589 * This function flattens the quote string returned to us by is_quote. A
590 * crash in this function implies a bug elsewhere.
592 void
593 flatten_qstring(QSTRING_S *qs, char *buff, int bufflen)
595 int i, j;
596 if(!buff || bufflen <= 0)
597 return;
599 for (i = 0; qs; qs = qs->next)
600 for (j = 0; i < bufflen - 1
601 && (qs->value[j]) && (buff[i++] = qs->value[j]); j++);
602 buff[i] = '\0';
605 extern int list_len;
609 double_check_qstr(char *q)
611 if(!q || !*q)
612 return 0;
614 return (*q == '#') ? 1 : 0;
618 * Given a string, we return the position where the function thinks that
619 * the quote string is over, if you are ever thinking of fixing something,
620 * you got to the right place. Memory freed by caller. Experience shows
621 * that it only makes sense to initialize memory when we need it, not at
622 * the start of this function.
624 QSTRING_S *
625 is_quote (char **qs,char *word, int been_here)
627 int i = 0, j, nxt, prev, finished = 0, offset;
628 unsigned char c;
629 QSTRING_S *qstring = (QSTRING_S *) NULL;
631 if (word == NULL || word[0] == '\0')
632 return (QSTRING_S *) NULL;
634 while (!finished){
636 * Before we apply our rules, let's advance past the quote string
637 * given by the user, this will avoid not recognition of the
638 * user's indent string and application of the arbitrary rules
639 * below. Notice that this step may bring bugs into this
640 * procedure, but these bugs will only appear if the indent string
641 * is really really strange and the text to be justified
642 * cooperates a lot too, so in general this will not be a problem.
643 * If you are concerned about this bug, simply remove the
644 * following lines after this comment and before the "switch"
645 * command below and use a more normal quote string!.
647 for(j = 0; j < list_len; j++){
648 if(!double_check_qstr(qs[j])){
649 i += advance_quote_string(qs[j], word, i);
650 if (!word[i]) /* went too far? */
651 return qs_add(qs, word, qsNormal, 0, i, 0, 0);
653 else
654 break;
657 switch (c = (unsigned char) now(word,i)){
658 case NBSP:
659 case TAB :
660 case ' ' : { QSTRING_S *nextqs, *d;
662 for (; ISspace(word[i]); i++); /* FIX ME */
663 nextqs = is_quote(qs,word+i, 1);
665 * Merge qstring and nextqs, since this is an artificial
666 * separation, unless nextqs is of different type.
667 * What this means in practice is that if
668 * qs->qstype == qsNormal and qs->next != NULL, then
669 * qs->next->qstype != qsNormal.
671 * Can't use qs_add to merge because it could lead
672 * to an infinite loop (e.g a line "^ ^").
674 i += nextqs && nextqs->qstype == qsNormal
675 ? strlen(nextqs->value) : 0;
676 qstring = (QSTRING_S *) malloc (sizeof(QSTRING_S));
677 memset (qstring, 0, sizeof(QSTRING_S));
678 qstring->value = (char *) malloc((i+1)*sizeof(char));
679 strncpy(qstring->value, word, i);
680 qstring->value[i] = '\0';
681 qstring->qstype = qsNormal;
682 if(nextqs && nextqs->qstype == qsNormal){
683 d = nextqs->next;
684 nextqs->next = NULL;
685 qstring->next = d;
686 free_qs(&nextqs);
688 else
689 qstring->next = nextqs;
691 return qstring;
693 break;
694 case RPAREN: /* parenthesis ')' */
695 if ((i != 0) || ((i == 0) && been_here))
696 i++;
697 else
698 if (i == 0)
699 return qs_add(qs, word, qsChar, i, i, 1, 1);
700 else
701 finished++;
702 break;
704 case ':': /* colon */
705 case '~': nxt = next(word,i);
706 if ((is_tilde(c) && (nxt == '/'))
707 || (is_colon(c) && !is_cquote(nxt)
708 && !is_cword(nxt) && nxt != RPAREN))
709 finished++;
710 else if (is_cquote(c)
711 || is_cquote(nxt)
712 || (c != '~' && nxt == RPAREN)
713 || (i != 0 && ISspace(nxt))
714 || is_cquote(prev = before(word,i))
715 || (ISspace(prev) && !is_tilde(c))
716 || (is_tilde(c) && nxt != '/'))
717 i++;
718 else if (i == 0 && been_here)
719 return qs_add(qs, word, qsChar, i, i, 1, 1);
720 else
721 finished++;
722 break;
724 case '<' :
725 case '=' :
726 case '-' : offset = is_cquote(nxt = next(word,i)) ? 2
727 : (nxt == c && is_cquote(next(word,i+1))) ? 3 : -1;
729 if (offset > 0)
730 return qs_add(qs, word, qsString, i, i, offset, 1);
731 else
732 finished++;
733 break;
735 case '[' :
736 case '+' : /* accept +>, *> */
737 case '*' : if (is_rarrow(nxt = next(word, i)) || /* stars */
738 (ISspace(nxt) && is_rarrow(next(word,i+1))))
739 i++;
740 else
741 finished++;
742 break;
744 case '^' :
745 case '!' :
746 case '%' : if (next(word,i) != c)
747 return qs_add(qs, word, qsChar, i, i+1, 0, 1);
748 else
749 finished++;
750 break;
752 case '_' : if(ISspace(next(word, i)))
753 return qs_add(qs, word, qsChar, i, i+1, 0, 1);
754 else
755 finished++;
756 break;
758 case '#' : { QStrType qstype = qsChar;
759 if((nxt = next(word, i)) != c){
760 if(isdigit((int) nxt))
761 qstype = qsGdb;
762 else
763 if(word_is_prog(word))
764 qstype = qsProg;
765 return qs_add(qs, word, qstype, i, i+1, 0, 1);
767 else
768 finished++;
769 break;
772 default:
773 if (is_cquote(c))
774 i++;
775 else if (is_cletter(c)){
776 for (j = i; (is_cletter(nxt = next(word,j)) || is_cnumber(nxt))
777 && !(ISspace(nxt));j++);
779 * The whole reason why we are splitting the quote
780 * string is so that we will be able to accept quote
781 * strings that are strange in some way. Here we got to
782 * a point in which a quote string might exist, but it
783 * could be strange, so we need to create a "next" field
784 * for the quote string to warn us that something
785 * strange is coming. We need to confirm if this is a
786 * good choice later. For now we will let it pass.
788 if (isaword(word,i,j) || isamailbox(word,i,j)){
789 int offset;
790 QStrType qstype;
792 offset = (is_cquote(c = next(word,j))
793 || (c == RPAREN)) ? 2
794 : ((ISspace(c)
795 && is_cquote(next(word,j+1))) ? 3 : -1);
797 qstype = (is_cquote(c) || (c == RPAREN))
798 ? (is_qsword(c) ? qsWord : qsString)
799 : ((ISspace(c) && is_cquote(next(word,j+1)))
800 ? (is_qsword(next(word,j+1))
801 ? qsWord : qsString)
802 : qsString);
805 * qsWords are valid quote strings only when
806 * they are followed by text.
808 if (offset > 0 && qstype == qsWord &&
809 !allwd_after_qsword(now(word,j + offset)))
810 offset = -1;
812 if (offset > 0)
813 return qs_add(qs, word, qstype, i, j, offset, 1);
815 finished++;
817 else{
818 if(i > 0)
819 return qs_add(qs, word, qsNormal, 0, i, 0, 1);
820 else if(!forbidden(c))
821 return qs_add(qs, word, qsChar, 0, 1, 0, 1);
822 else /* chao pescao */
823 finished++;
825 break;
826 } /* End Switch */
827 } /* End while */
828 if (i > 0)
829 qstring = qs_add(qs, word, qsNormal, 0, i, 0, 0);
830 return qstring;
834 isaword(char word[NSTRING], int i, int j)
836 return i <= j && is_cletter(word[i]) ?
837 (i < j ? isaword(word,i+1,j) : 1) : 0;
841 isamailbox(char word[NSTRING], int i, int j)
843 return i <= j && (is_cletter(word[i]) || is_a_digit(word[i])
844 || word[i] == '.')
845 ? (i < j ? isamailbox(word,i+1,j) : 1) : 0;
849 This routine removes the last part that is qsword or qschar that is not
850 followed by a normal part. This means that if a qsword or qschar is
851 followed by a qsnormal (or qsstring), we accept the qsword (or qschar)
852 as part of a quote string.
854 QSTRING_S *
855 remove_qsword(QSTRING_S *cl)
857 QSTRING_S *np = cl;
858 QSTRING_S *cp = np; /* this variable trails cl */
860 while(1){
861 while (cl && cl->qstype == qsNormal)
862 cl = cl->next;
864 if (cl){
865 if (((cl->qstype == qsWord) || (cl->qstype == qsChar))
866 && !exists_good_part(cl)){
867 if (np == cl) /* qsword or qschar at the beginning */
868 free_qs(&cp);
869 else{
870 while (np->next != cl)
871 np = np->next;
872 free_qs(&(np->next));
874 break;
876 else
877 cl = cl->next;
879 else
880 break;
882 return cp;
886 exists_good_part (QSTRING_S *cl)
888 return (cl ? (((cl->qstype != qsWord) && (cl->qstype != qsChar)
889 && qs_allowed(cl) && !value_is_space(cl->value))
890 ? 1 : exists_good_part(cl->next))
891 : 0);
895 line_isblank(char **q, char *GLine, char *NLine, char *PLine, int buflen)
897 int n = 0;
898 QSTRING_S *cl;
899 char qstr[NSTRING];
901 cl = do_raw_quote_match(q, GLine, NLine, PLine, NULL, NULL);
903 flatten_qstring(cl, qstr, NSTRING);
905 free_qs(&cl);
907 for(n = strlen(qstr); n < buflen && GLine[n]; n++)
908 if(!ISspace((unsigned char) GLine[n]))
909 return(FALSE);
911 return(TRUE);
914 QSTRING_S *
915 do_raw_quote_match(char **q, char *GLine, char *NLine, char *PLine, QSTRING_S **nlp, QSTRING_S **plp)
917 QSTRING_S *cl, *nl = NULL, *pl = NULL;
918 char nbuf[NSTRING], pbuf[NSTRING], buf[NSTRING];
919 int emptypl = 0, emptynl = 0;
921 if (!(cl = is_quote(q, GLine, 0))) /* if nothing in, nothing out */
922 return cl;
924 nl = is_quote(q, NLine, 0); /* Next Line */
925 if (nlp) *nlp = nl;
926 pl = is_quote(q, PLine, 0); /* Previous Line */
927 if (plp) *plp = pl;
929 * If there's nothing in the preceeding or following line
930 * there is not enough information to accept it or discard it. In this
931 * case it's likely to be an isolated line, so we better accept it
932 * if it does not look like a word.
934 flatten_qstring(pl, pbuf, NSTRING);
935 emptypl = (!PLine || !PLine[0] ||
936 (pl && value_is_space(pbuf)) && !PLine[strlen(pbuf)]) ? 1 : 0;
937 if (emptypl){
938 flatten_qstring(nl, nbuf, NSTRING);
939 emptynl = (!NLine || !NLine[0] ||
940 (nl && value_is_space(nbuf) && !NLine[strlen(nbuf)])) ? 1 : 0;
941 if (emptynl){
942 cl = remove_qsword(cl);
943 if((cl = fix_qstring_allowed(cl, NULL, NULL)) != NULL)
944 cl = qs_remove_trailing_spaces(cl);
945 free_qs(&nl);
946 free_qs(&pl);
947 if(nlp) *nlp = NULL;
948 if(plp) *plp = NULL;
950 return cl;
955 * If either cl, nl or pl contain suspicious characters that may make
956 * them (or not) be quote strings, we need to fix them, so that the
957 * next pass will be done correctly.
960 cl = fix_qstring(cl, nl, pl);
961 nl = trim_qs_from_cl(cl, nl, NULL);
962 pl = trim_qs_from_cl(cl, NULL, pl);
963 if((cl = fix_qstring_allowed(cl, nl, pl)) != NULL){
964 nl = trim_qs_from_cl(cl, nl, NULL);
965 pl = trim_qs_from_cl(cl, NULL, pl);
967 else{
968 free_qs(&nl);
969 free_qs(&pl);
971 if(nlp)
972 *nlp = nl;
973 else
974 free_qs(&nl);
975 if(plp)
976 *plp = pl;
977 else
978 free_qs(&pl);
979 return cl;
982 QSTRING_S *
983 do_quote_match(char **q, char *GLine, char *NLine, char *PLine, char *rqstr,
984 int rqstrlen, int plb)
986 QSTRING_S *cl, *nl = NULL, *pl = NULL;
987 int c, n, p,i, j, NewP, NewC, NewN, clength, same = 0;
988 char nbuf[NSTRING], pbuf[NSTRING], buf[NSTRING];
990 if(rqstr)
991 *rqstr = '\0';
993 /* if nothing in, nothing out */
994 cl = do_raw_quote_match(q, GLine, NLine, PLine, &nl, &pl);
995 if(cl == NULL){
996 free_qs(&nl);
997 free_qs(&pl);
998 return cl;
1001 flatten_qstring(cl, rqstr, rqstrlen);
1002 flatten_qstring(cl, buf, NSTRING);
1003 flatten_qstring(nl, nbuf, NSTRING);
1004 flatten_qstring(pl, pbuf, NSTRING);
1007 * Once upon a time, is_quote used to return the length of the quote
1008 * string that it had found. One day, not long ago, black hand came
1009 * and changed all that, and made is_quote return a quote string
1010 * divided in several fields, making the algorithm much more
1011 * complicated. Fortunately black hand left a few comments in the
1012 * source code to make it more understandable. Because of this change
1013 * we need to compute the lengths of the quote strings separately
1015 c = buf && buf[0] ? strlen(buf) : 0;
1016 n = nbuf && nbuf[0] ? strlen(nbuf) : 0;
1017 p = pbuf && pbuf[0] ? strlen(pbuf) : 0;
1019 * When quote strings contain only blank spaces (ascii code 32) the
1020 * above count is equal to the length of the quote string, but if
1021 * there are TABS, the length of the quote string as seen by the user
1022 * is different than the number that was just computed. Because of
1023 * this we demand a recount (hmm.. unless you are in Florida, where
1024 * recounts are forbidden)
1026 NewP = strlenis(pbuf);
1027 NewC = strlenis(buf);
1028 NewN = strlenis(nbuf);
1031 * For paragraphs with spaces in the first line, but no space in the
1032 * quote string of the second line, we make sure we choose the quote
1033 * string without a space at the end of it.
1035 if ((NLine && !NLine[0])
1036 && ((PLine && !PLine[0])
1037 || (((same = same_qstring(pl, cl)) != 0)
1038 && (same != count_levels_qstring(cl)))))
1039 cl = qs_remove_trailing_spaces(cl);
1040 else
1041 if (NewC > NewN){
1042 int agree = 0;
1043 for (j = 0; (j < n) && (GLine[j] == NLine[j]); j++);
1044 clength = j;
1045 /* clength is the common length in which Gline and Nline agree */
1046 /* j < n means that they do not agree fully */
1047 /* GLine = " \tText"
1048 NLine = " Text" */
1049 if(j == n)
1050 agree++;
1051 if (clength < n){ /* see if buf and nbuf are padded with spaces and tabs */
1052 for (i = clength; i < n && ISspace(NLine[i]); i++);
1053 if (i == n){/* padded NLine until the end of spaces? */
1054 for (i = clength; i < c && ISspace(GLine[i]); i++);
1055 if (i == c) /* Padded CLine until the end of spaces? */
1056 agree++;
1059 if (agree){
1060 for (j = clength; j < c && ISspace(GLine[j]); j++);
1061 if (j == c){
1063 * If we get here, it means that the current line has the same
1064 * quote string (visually) than the next line, but both of them
1065 * are padded with different amount of TABS or spaces at the end.
1066 * The current line (GLine) has more spaces/TABs than the next
1067 * line. This is the typical situation that is found at the
1068 * begining of a paragraph. We need to check this, however, by
1069 * checking the previous line. This avoids that we confuse
1070 * ourselves with being in the last line of a paragraph.
1071 * Example when it should not free_qs(cl)
1072 * " Text in Paragraph 1" (PLine)
1073 * " Text in Paragraph 1" (GLine)
1074 * " Other Paragraph Number 2" (NLine)
1076 * Example when it should free_qs(cl):
1077 * ":) " (PLine) p = 3, j = 3
1078 * ":) Text" (GLine) c = 5
1079 * ":) More text" (NLine) n = 3
1081 * Example when it should free_qs(cl):
1082 * ":) " (PLine) p = 3, j = 3
1083 * ":) > > > Text" (GLine) c = 11
1084 * ":) > > > More text" (NLine) n = 9
1086 * Example when it should free_qs(cl):
1087 * ":) :) " (PLine) p = 6, j = 3
1088 * ":) > > > Text" (GLine) c = 11
1089 * ":) > > > More text" (NLine) n = 9
1091 * Example when it should free_qs(cl):
1092 * ":) > > > " (PLine) p = 13, j = 11
1093 * ":) > > > Text" (GLine) c = 11
1094 * ":) > > > More text" (NLine) n = 9
1096 * The following example is very interesting. The "Other Text"
1097 * line below should free the quote string an make it equal to the
1098 * quote string of the line below it, but any algorithm trying
1099 * to advance past that line should make it stop there, so
1100 * we need one more check, to check the raw quote string and the
1101 * processed quote string at the same time.
1102 * FREE qs in this example.
1103 * " Some Text" (PLine) p = 3, j = 0
1104 * "\tOther Text" (GLine) c = 1
1105 * " More Text" (NLine) n = 3
1108 for (j = 0; (j < p) && (GLine[j] == PLine[j]); j++);
1109 if ((p != c || j != p) && NLine[n])
1110 if(!get_indent_raw_line(q, PLine, nbuf, NSTRING, p, plb)
1111 || NewP + strlenis(nbuf) != NewC){
1112 free_qs(&cl);
1113 free_qs(&pl);
1114 return nl;
1120 free_qs(&nl);
1121 free_qs(&pl);
1123 return cl;
1127 * Given a line, an initial position, and a quote string, we advance the
1128 * current line past the quote string, including arbitraty spaces
1129 * contained in the line, except that it removes trailing spaces. We do
1130 * not handle TABs, if any, contained in the quote string. At least not
1131 * yet.
1133 * Arguments: q - quote string
1134 * l - a line to process
1135 * i - position in the line to start processing. i = 0 is the
1136 * begining of that line.
1139 advance_quote_string(char *q, char l[NSTRING], int i)
1141 int n = 0, j = 0, is = 0, es = 0;
1142 int k, m, p, adv;
1143 char qs[NSTRING] = {'\0'};
1144 if(!q || !*q)
1145 return(0);
1146 for (p = strlen(q); (p > 0) && (q[p - 1] == ' '); p--, es++);
1147 if (!p){ /* string contains only spaces */
1148 for (k = 0; ISspace(l[i + k]); k++);
1149 k -= k % es;
1150 return k;
1152 for (is = 0; ISspace(q[is]); is++); /* count initial spaces */
1153 for (m = 0 ; is + m < p ; m++)
1154 qs[m] = q[is + m]; /* qs = quote string without any space at the end */
1155 /* advance as many spaces as there are at the begining */
1156 for (k = 0; ISspace(l[i + j]); k++, j++);
1157 /* now find the visible string in the line */
1158 for (m = 0; qs[m] && l[i + j] == qs[m]; m++, j++);
1159 if (!qs[m]){ /* no match */
1161 * So far we have advanced at least "is" spaces, plus the visible
1162 * string "qs". Now we need to advance the trailing number of
1163 * spaces "es". If we can do that, we have found the quote string.
1165 for (p = 0; ISspace(l[i + j + p]); p++);
1166 adv = advance_quote_string(q, l, i + j + ((p < es) ? p : es));
1167 n = ((p < es) ? 0 : es) + k + m + adv;
1169 return n;
1173 * This function returns the effective length in screen of the quote
1174 * string. If the string contains a TAB character, it is added here, if
1175 * not, the length returned is the length of the string
1177 int strlenis(char *qstr)
1179 int i, rv = 0;
1180 for (i = 0; qstr && qstr[i]; i++)
1181 rv += ((qstr[i] == TAB) ? (~rv & 0x07) + 1 : 1);
1182 return rv;
1186 is_indent (char word[NSTRING], int plb)
1188 int i = 0, finished = 0, c, nxt, j, k, digit = 0, bdigits = -1, alpha = 0;
1190 if (!word || !word[0])
1191 return i;
1193 for (i = 0, j = 0; ISspace(word[i]); i++, j++);
1194 while ((i < NSTRING - 2) && !finished){
1195 switch (c = now(word,i)){
1196 case NBSP:
1197 case TAB :
1198 case ' ' : for (; ISspace(word[i]); i++);
1199 if (!is_indent_char(now(word,i)))
1200 finished++;
1201 break;
1203 case '+' :
1204 case '.' :
1205 case ']' :
1206 case '*' :
1207 case '}' :
1208 case '-' :
1209 case RPAREN:
1210 nxt = next(word,i);
1211 if ((c == '.' && allowed_after_period(nxt) && alpha)
1212 || (c == '*' && allowed_after_star(nxt))
1213 || (c == '}' && allowed_after_braces(nxt))
1214 || (c == '-' && allowed_after_dash(nxt))
1215 || (c == '+' && allowed_after_dash(nxt))
1216 || (c == RPAREN && allowed_after_parenth(nxt))
1217 || (c == ']' && allowed_after_parenth(nxt)))
1218 i++;
1219 else
1220 finished++;
1221 break;
1223 default : if (is_a_digit(c) && plb){
1224 if (bdigits < 0)
1225 bdigits = i; /* first digit */
1226 for (k = i; is_a_digit(now(word,k)); k++);
1227 if (k - bdigits > 2){ /* more than 2 digits? */
1228 i = bdigits; /* too many! */
1229 finished++;
1231 else{
1232 if(allowed_after_digit(now(word,k),word,k)){
1233 alpha++;
1234 i = k;
1236 else{
1237 i = bdigits;
1238 finished++;
1242 else
1243 finished++;
1244 break;
1248 if (i == j)
1249 i = 0; /* there must be something more than spaces in an indent string */
1250 return i;
1254 get_indent_raw_line(char **q, char *GLine, char *buf, int buflen, int k, int plb)
1256 int i, j;
1257 char testline[1024];
1259 if(k > 0){
1260 for(j = 0; GLine[j] != '\0'; j++){
1261 testline[j] = GLine[j];
1262 testline[j+1] = '\0';
1263 if(strlenis(testline) >= strlenis(buf))
1264 break;
1266 k = ++j; /* reset k */
1268 i = is_indent(GLine+k, plb);
1270 for (j = 0; j < i && j < buflen && (buf[j] = GLine[j + k]); j++);
1271 buf[j] = '\0';
1273 return i;
1276 /* support for remembering quote strings across messages */
1277 char **allowed_qstr = NULL;
1278 int list_len = 0;
1280 void
1281 free_allowed_qstr(void)
1283 int i;
1284 char **q = allowed_qstr;
1286 if(q == NULL)
1287 return;
1289 for(i = 0; i < list_len; i++)
1290 fs_give((void **)&q[i]);
1292 fs_give((void **)q);
1293 list_len = 0;
1296 void
1297 add_allowed_qstr(void *q, int type)
1299 int i;
1301 if(allowed_qstr == NULL){
1302 allowed_qstr = malloc(sizeof(char *));
1303 list_len = 0;
1306 if(type == 0){
1307 allowed_qstr[list_len] = malloc((1+strlen((char *)q))*sizeof(char));
1308 strcpy(allowed_qstr[list_len], (char *)q);
1310 else
1311 allowed_qstr[list_len] = (char *) ucs4_to_utf8_cpystr((UCS *)q);
1313 fs_resize((void **)&allowed_qstr, (++list_len + 1)*sizeof(char *));
1314 allowed_qstr[list_len] = NULL;
1317 void
1318 record_quote_string (QSTRING_S *qs)
1320 int i, j, k;
1322 for(; qs && qs->value; qs = qs->next){
1323 j = 0;
1324 for (; ;){
1325 k = j;
1326 for(i = 0; i < list_len; i++){
1327 j += advance_quote_string(allowed_qstr[i], qs->value, j);
1328 for(; ISspace(qs->value[j]); j++);
1330 if(k == j)
1331 break;
1333 if(qs->value[j] != '\0')
1334 add_allowed_qstr((void *)(qs->value + j), 0);
1338 /* type utf8: code 0; ucs4: code 1. */
1339 char **
1340 default_qstr(void *q, int type)
1342 if(allowed_qstr == NULL)
1343 add_allowed_qstr(q, type);
1345 return allowed_qstr;