1 /* tag.c -- Functions to handle Info tags (that is, the special
2    construct for images, not the "tag table" of starting position.)
3 
4    Copyright 2012-2019 Free Software Foundation, Inc.
5 
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation, either version 3 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
18 
19 #include "info.h"
20 #include "tag.h"
21 #include "info-utils.h"
22 
23 struct tag_handler
24 {
25   const char *name;
26   size_t len;
27   int (*handler) (char *, struct text_buffer *);
28 };
29 
30 struct info_tag
31 {
32   struct info_tag *next;
33   char *kw;
34   char *val;
35 };
36 
37 static void
info_tag_free(struct info_tag * tag)38 info_tag_free (struct info_tag *tag)
39 {
40   while (tag)
41     {
42       struct info_tag *next = tag->next;
43       free (tag->kw);
44       free (tag->val);
45       free (tag);
46       tag = next;
47     }
48 }
49 
50 
51 /* See if KW is one of the tags in the list starting at TAG.  */
52 
53 static struct info_tag *
info_tag_find(struct info_tag * tag,const char * kw)54 info_tag_find (struct info_tag *tag, const char *kw)
55 {
56   for (; tag; tag = tag->next)
57     if (strcmp (tag->kw, kw) == 0)
58       return tag;
59   return NULL;
60 }
61 
62 
63 /* Found a keyword when parsing the full tag string: alt, text, etc.
64    Return the new tag, update *TMPBUF_PTR and set *KW.  */
65 
66 static struct info_tag *
tag_found_keyword(struct text_buffer * tmpbuf_ptr,char ** kw)67 tag_found_keyword (struct text_buffer *tmpbuf_ptr, char **kw)
68 {
69   struct info_tag *tag = xmalloc (sizeof (*tag));
70   tag->next = NULL;  /* have to update in caller */
71 
72   text_buffer_add_char (tmpbuf_ptr, 0);
73   if (*kw != tmpbuf_ptr->base) { /* in case tmpbuf got realloc-ed */
74     *kw = tmpbuf_ptr->base;      /* ick */
75   }
76   tag->kw = xstrdup (*kw);
77   tag->val = xstrdup (*kw + strlen(*kw) + 1);
78   text_buffer_reset (tmpbuf_ptr);
79 
80   return tag;
81 }
82 
83 /* Handle the image tag.  */
84 
85 static int
tag_image(char * text,struct text_buffer * outbuf)86 tag_image (char *text, struct text_buffer *outbuf)
87 {
88   mbi_iterator_t iter;
89   enum { state_kw, state_val, state_qstr, state_delim } state = state_kw;
90   struct text_buffer tmpbuf;
91   char *kw;
92   struct info_tag *tag_head = NULL, *tag;
93   int escaped = 0;
94 
95   text_buffer_init (&tmpbuf);
96   for (mbi_init (iter, text, strlen (text)); mbi_avail (iter);
97        mbi_advance (iter))
98     {
99       const char *cur_ptr;
100       size_t cur_len;
101 
102       if (mb_isspace (mbi_cur (iter)))
103 	{
104 	  if (state == state_val)
105 	    {
106               struct info_tag *new_kw = tag_found_keyword (&tmpbuf, &kw);
107               new_kw->next = tag_head;
108               tag_head = new_kw;
109               state = state_delim;
110               continue;
111 	    }
112 	  if (state == state_delim)
113 	    continue;
114 	}
115       else if (state == state_delim)
116 	state = state_kw;
117       cur_len = mb_len (mbi_cur (iter));
118       cur_ptr = mbi_cur_ptr (iter);
119 
120       if (state == state_qstr && escaped)
121 	{
122 	  escaped = 0;
123 	}
124       else if (cur_len == 1)
125 	{
126 	  switch (*cur_ptr)
127 	    {
128 	    case '=':
129 	      if (state != state_kw)
130 		break;
131 	      text_buffer_add_char (&tmpbuf, 0);
132 	      kw = tmpbuf.base;
133 	      if (!mbi_avail (iter))
134 		break;
135 	      mbi_advance (iter);
136 	      state = state_val;
137 	      cur_len = mb_len (mbi_cur (iter));
138 	      cur_ptr = mbi_cur_ptr (iter);
139 	      if (!(cur_len == 1 && *cur_ptr == '"'))
140 		break;
141 	      /* fall through */
142 
143 	    case '"':
144 	      if (state == state_val)
145 		{
146 		  state = state_qstr;
147 		  continue;
148 		}
149 	      if (state == state_qstr)
150 		{
151 		  struct info_tag *new_kw = tag_found_keyword (&tmpbuf, &kw);
152 		  new_kw->next = tag_head;
153 		  tag_head = new_kw;
154 		  state = state_delim;
155 		  continue;
156 		}
157 	      break;
158 
159 	    case '\\':
160 	      if (state == state_qstr)
161 		{
162 		  escaped = 1;
163 		  continue;
164 		}
165 	    }
166 	}
167       text_buffer_add_string (&tmpbuf, cur_ptr, cur_len);
168     }
169 
170   tag = info_tag_find (tag_head, "text");
171   if (!tag)
172     tag = info_tag_find (tag_head, "alt");
173 
174   if (tag)
175     {
176       text_buffer_add_string (outbuf, tag->val, strlen (tag->val));
177     }
178 
179   text_buffer_free (&tmpbuf);
180   info_tag_free (tag_head);
181   return 0;
182 }
183 
184 
185 /* We don't do anything with the index tag; it'll just be ignored.  */
186 
187 static struct tag_handler tagtab[] = {
188   { "image", 5, tag_image },
189   { "index", 5, NULL },
190   { NULL }
191 };
192 
193 static struct tag_handler *
find_tag_handler(char * tag,size_t taglen)194 find_tag_handler (char *tag, size_t taglen)
195 {
196   struct tag_handler *tp;
197 
198   for (tp = tagtab; tp->name; tp++)
199     if (taglen >= tp->len && strncmp (tp->name, tag, tp->len) == 0)
200       return tp;
201   return NULL;
202 }
203 
204 /* Expand \b[...\b] construct at *INPUT.  If encountered, append the
205    expanded text to OUTBUF, advance *INPUT past the tag, and return 1.
206    Otherwise, return 0.  If it is an index tag, set IS_INDEX to 1.
207    *INPUT points into a null-terminated area which may however contain other
208    null characters.  INPUT_END points to the end of this area. */
209 int
tag_expand(char ** input,char * input_end,struct text_buffer * outbuf,int * is_index)210 tag_expand (char **input, char *input_end,
211             struct text_buffer *outbuf, int *is_index)
212 {
213   char *p = *input;
214   char *q;
215   size_t len;
216   struct tag_handler *tp;
217 
218   if (p >= input_end - 3
219     || memcmp(p, "\0\b[", 3) != 0)       /* opening magic? */
220     return 0;
221 
222   p += 3;
223   q = p + strlen (p);
224   if (q >= input_end - 3
225       || memcmp (q + 1, "\b]", 2)) /* closing magic? */
226     return 0; /* Not a proper tag. */
227 
228   /* Output is different for index nodes */
229   if (!strncmp ("index", p, strlen ("index")))
230     *is_index = 1;
231 
232   len = strcspn (p, " \t");       /* tag name */
233   tp = find_tag_handler (p, len);
234   if (tp && tp->handler)
235     {
236       while (p[len] == ' ' || p[len] == '\t')
237         ++len;                      /* move past whitespace */
238 
239       tp->handler (p + len, outbuf);
240     }
241   *input = q + 3;
242   return 1;
243 }
244