1 /*        $NetBSD: type_enum.c,v 1.13 2021/04/13 13:13:04 christos Exp $        */
2 
3 /*-
4  * Copyright (c) 1998-1999 Brett Lymn
5  *                         (blymn@baea.com.au, brett_lymn@yahoo.com.au)
6  * All rights reserved.
7  *
8  * This code has been donated to The NetBSD Foundation by the Author.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. The name of the author may not be used to endorse or promote products
16  *    derived from this software without specific prior written permission
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  *
29  *
30  */
31 
32 #include <sys/cdefs.h>
33 __RCSID("$NetBSD: type_enum.c,v 1.13 2021/04/13 13:13:04 christos Exp $");
34 
35 #include <ctype.h>
36 #include <stdlib.h>
37 #include <strings.h>
38 #include "form.h"
39 #include "internals.h"
40 
41 /*
42  * Prototypes.
43  */
44 static int
45 trim_blanks(char *field);
46 
47 /*
48  * The enum type handling.
49  */
50 
51 typedef struct
52 {
53           char **choices;
54           unsigned num_choices;
55           bool ignore_case;
56           bool exact;
57 } enum_args;
58 
59 /*
60  * Find the first non-blank character at the end of a field, return the
61  * index of that character.
62  */
63 static int
trim_blanks(char * field)64 trim_blanks(char *field)
65 {
66           int i;
67 
68           i = (int) strlen(field);
69           if (i > 0)
70                     i--;
71           else
72                     return 0;
73 
74           while ((i > 0) && isblank((unsigned char)field[i]))
75                     i--;
76 
77           return i;
78 }
79 
80 /*
81  * Create the enum arguments structure from the given args.  Return NULL
82  * if the call fails, otherwise return a pointer to the structure allocated.
83  */
84 static char *
create_enum_args(va_list * args)85 create_enum_args(va_list *args)
86 {
87           enum_args *new;
88           char **choices;
89 
90           new = malloc(sizeof(*new));
91           if (new == NULL)
92                     return NULL;
93 
94           new->choices = va_arg(*args, char **);
95           new->ignore_case = (va_arg(*args, int)) ? TRUE : FALSE;
96           new->exact = (va_arg(*args, int)) ? TRUE : FALSE;
97 
98           _formi_dbg_printf("%s: ignore_case %d, no_blanks %d\n", __func__,
99               new->ignore_case, new->exact);
100 
101             /* count the choices we have */
102           choices = new->choices;
103           new->num_choices = 0;
104           while (*choices != NULL) {
105                     _formi_dbg_printf("%s: choice[%u] = \'%s\'\n", __func__,
106                         new->num_choices, new->choices[new->num_choices]);
107                     new->num_choices++;
108                     choices++;
109           }
110           _formi_dbg_printf("%s: have %u choices\n", __func__,
111               new->num_choices);
112 
113           return (void *) new;
114 }
115 
116 /*
117  * Copy the enum argument structure.
118  */
119 static char *
copy_enum_args(char * args)120 copy_enum_args(char *args)
121 {
122           enum_args *new;
123 
124           new = malloc(sizeof(*new));
125 
126           if (new != NULL)
127                     memcpy(new, args, sizeof(*new));
128 
129           return (void *) new;
130 }
131 
132 /*
133  * Free the allocated storage associated with the type arguments.
134  */
135 static void
free_enum_args(char * args)136 free_enum_args(char *args)
137 {
138           if (args != NULL)
139                     free(args);
140 }
141 
142 /*
143  * Attempt to match the string in this to the choices given.  Returns
144  * TRUE if match found otherwise FALSE.
145  *
146  */
147 static bool
match_enum(char ** choices,unsigned num_choices,bool ignore_case,bool exact,char * this,unsigned * match_num)148 match_enum(char **choices, unsigned num_choices, bool ignore_case,
149              bool exact, char *this, unsigned *match_num)
150 {
151           unsigned i, start, end, enum_start, blen, elen, enum_end;
152           bool cur_match;
153 
154           start = _formi_skip_blanks(this, 0);
155           end = trim_blanks(this);
156 
157           if (end >= start)
158                     blen = (unsigned) (strlen(&this[start])
159                                            - strlen(&this[end]) + 1);
160           else
161                     blen = 0;
162 
163           _formi_dbg_printf("%s: start %u, blen %u\n", __func__, start, blen);
164           for (i = 0; i < num_choices; i++) {
165                     enum_start = _formi_skip_blanks(choices[i], 0);
166                     enum_end = trim_blanks(choices[i]);
167 
168                     if (enum_end >= enum_start)
169                               elen = (unsigned) (strlen(&choices[i][enum_start])
170                                         - strlen(&choices[i][enum_end]) + 1);
171                     else
172                               elen = 0;
173 
174                     _formi_dbg_printf("%s: checking choice \'%s\'\n", __func__,
175                               choices[i]);
176                     _formi_dbg_printf("%s: enum_start %u, elen %u\n", __func__,
177                               enum_start, elen);
178 
179                       /* don't bother if we are after an exact match
180                        * and the test length is not equal to the enum
181                        * in question - it will never match.
182                        */
183                     if ((exact == TRUE) && (blen != elen))
184                               continue;
185 
186                       /*
187                        * If the test length is longer than the enum
188                        * length then there is no chance of a match
189                        * so we skip.
190                        */
191                     if ((exact != TRUE) && (blen > elen))
192                               continue;
193 
194                     if (ignore_case)
195                               cur_match = (strncasecmp(&choices[i][enum_start],
196                                                              &this[start],
197                                                              (size_t)blen) == 0) ?
198                                         TRUE : FALSE;
199                     else
200                               cur_match = (strncmp(&choices[i][enum_start],
201                                                        &this[start],
202                                                        (size_t) blen) == 0) ?
203                                         TRUE : FALSE;
204 
205                     _formi_dbg_printf("%s: curmatch is %s\n", __func__,
206                               (cur_match == TRUE)? "TRUE" : "FALSE");
207 
208                     if (cur_match == TRUE) {
209                               *match_num = i;
210                               return TRUE;
211                     }
212 
213           }
214 
215           _formi_dbg_printf("%s: no match found\n", __func__);
216           return FALSE;
217 }
218 
219 /*
220  * Check the contents of the field buffer match one of the enum strings only.
221  */
222 static int
enum_check_field(FIELD * field,char * args)223 enum_check_field(FIELD *field, char *args)
224 {
225           enum_args *ta;
226           unsigned match_num;
227 
228           if (args == NULL)
229                     return FALSE;
230 
231           ta = (enum_args *) (void *) field->args;
232 
233           if (match_enum(ta->choices, ta->num_choices, ta->ignore_case,
234                            ta->exact, args, &match_num) == TRUE) {
235                     _formi_dbg_printf("%s: We matched, match_num %u\n", __func__,
236                         match_num);
237                     _formi_dbg_printf("%s: buffer is \'%s\'\n", __func__,
238                         ta->choices[match_num]);
239                     set_field_buffer(field, 0, ta->choices[match_num]);
240                     return TRUE;
241           }
242 
243           return FALSE;
244 }
245 
246 /*
247  * Get the next enum in the list of choices.
248  */
249 static int
next_enum(FIELD * field,char * args)250 next_enum(FIELD *field, char *args)
251 {
252           enum_args *ta;
253           unsigned cur_choice;
254 
255           if (args == NULL)
256                     return FALSE;
257 
258           ta = (enum_args *) (void *) field->args;
259 
260           _formi_dbg_printf("%s: attempt to match \'%s\'\n", __func__, args);
261 
262           if (match_enum(ta->choices, ta->num_choices, ta->ignore_case,
263                            ta->exact, args, &cur_choice) == FALSE) {
264                     _formi_dbg_printf("%s: match failed\n", __func__);
265                     return FALSE;
266           }
267 
268           _formi_dbg_printf("%s: cur_choice is %u\n", __func__, cur_choice);
269 
270           cur_choice++;
271 
272           if (cur_choice >= ta->num_choices)
273                     cur_choice = 0;
274 
275           _formi_dbg_printf("%s: cur_choice is %u on exit\n", __func__,
276               cur_choice);
277 
278           set_field_buffer(field, 0, ta->choices[cur_choice]);
279           return TRUE;
280 }
281 
282 /*
283  * Get the previous enum in the list of choices.
284  */
285 static int
prev_enum(FIELD * field,char * args)286 prev_enum(FIELD *field, char *args)
287 {
288           enum_args *ta;
289           unsigned cur_choice;
290 
291           if (args == NULL)
292                     return FALSE;
293 
294           ta = (enum_args *) (void *) field->args;
295 
296           _formi_dbg_printf("%s: attempt to match \'%s\'\n", __func__, args);
297 
298           if (match_enum(ta->choices, ta->num_choices, ta->ignore_case,
299                            ta->exact, args, &cur_choice) == FALSE) {
300                     _formi_dbg_printf("%s: match failed\n", __func__);
301                     return FALSE;
302           }
303 
304           _formi_dbg_printf("%s: cur_choice is %u\n", __func__, cur_choice);
305           if (cur_choice == 0)
306                     cur_choice = ta->num_choices - 1;
307           else
308                     cur_choice--;
309 
310           _formi_dbg_printf("%s: cur_choice is %u on exit\n",
311               __func__, cur_choice);
312 
313           set_field_buffer(field, 0, ta->choices[cur_choice]);
314           return TRUE;
315 }
316 
317 
318 static FIELDTYPE builtin_enum = {
319           _TYPE_HAS_ARGS | _TYPE_IS_BUILTIN,  /* flags */
320           0,                                  /* refcount */
321           NULL,                               /* link */
322           create_enum_args,                  /* make_args */
323           copy_enum_args,                    /* copy_args */
324           free_enum_args,                    /* free_args */
325           enum_check_field,                  /* field_check */
326           NULL,                              /* char_check */
327           next_enum,                         /* next_choice */
328           prev_enum                          /* prev_choice */
329 };
330 
331 FIELDTYPE *TYPE_ENUM = &builtin_enum;
332 
333 
334