libressl: Fix validation errors in certificate chains with expired certificates
[dragonfly.git] / contrib / dialog / editbox.c
1 /*
2  *  $Id: editbox.c,v 1.79 2020/03/27 21:04:47 tom Exp $
3  *
4  *  editbox.c -- implements the edit box
5  *
6  *  Copyright 2007-2019,2020 Thomas E. Dickey
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU Lesser General Public License, version 2.1
10  *  as published by the Free Software Foundation.
11  *
12  *  This program is distributed in the hope that it will be useful, but
13  *  WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public
18  *  License along with this program; if not, write to
19  *      Free Software Foundation, Inc.
20  *      51 Franklin St., Fifth Floor
21  *      Boston, MA 02110, USA.
22  */
23
24 #include <dialog.h>
25 #include <dlg_keys.h>
26
27 #include <sys/stat.h>
28
29 #define sTEXT -1
30
31 static void
32 fail_list(void)
33 {
34     dlg_exiterr("File too large");
35 }
36
37 static void
38 grow_list(char ***list, int *have, int want)
39 {
40     if (want > *have) {
41         size_t last = (size_t) *have;
42         size_t need = (size_t) (want | 31) + 3;
43         *have = (int) need;
44         (*list) = dlg_realloc(char *, need, *list);
45         if ((*list) == 0) {
46             fail_list();
47         } else {
48             while (++last < need) {
49                 (*list)[last] = 0;
50             }
51         }
52     }
53 }
54
55 static void
56 load_list(const char *file, char ***list, int *rows)
57 {
58     char *blob = 0;
59     struct stat sb;
60     size_t size;
61
62     *list = 0;
63     *rows = 0;
64
65     if (stat(file, &sb) < 0 ||
66         (sb.st_mode & S_IFMT) != S_IFREG)
67         dlg_exiterr("Not a file: %s", file);
68
69     size = (size_t) sb.st_size;
70     if ((blob = dlg_malloc(char, size + 2)) == 0) {
71         fail_list();
72     } else {
73         FILE *fp;
74         unsigned n, pass;
75
76         blob[size] = '\0';
77
78         if ((fp = fopen(file, "r")) == 0)
79             dlg_exiterr("Cannot open: %s", file);
80         size = fread(blob, sizeof(char), size, fp);
81         fclose(fp);
82
83         /*
84          * If the file is not empty, ensure that it ends with a newline.
85          */
86         if (size != 0 && blob[size - 1] != '\n') {
87             blob[++size - 1] = '\n';
88             blob[size] = '\0';
89         }
90
91         for (pass = 0; pass < 2; ++pass) {
92             int first = TRUE;
93             unsigned need = 0;
94
95             for (n = 0; n < size; ++n) {
96                 if (first && pass) {
97                     (*list)[need] = blob + n;
98                     first = FALSE;
99                 }
100                 if (blob[n] == '\n') {
101                     first = TRUE;
102                     ++need;
103                     if (pass)
104                         blob[n] = '\0';
105                 }
106             }
107             if (pass) {
108                 if (need == 0) {
109                     (*list)[0] = dlg_strclone("");
110                     (*list)[1] = 0;
111                 } else {
112                     for (n = 0; n < need; ++n) {
113                         (*list)[n] = dlg_strclone((*list)[n]);
114                     }
115                     (*list)[need] = 0;
116                 }
117             } else {
118                 grow_list(list, rows, (int) need + 1);
119             }
120         }
121         free(blob);
122     }
123 }
124
125 static void
126 free_list(char ***list, int *rows)
127 {
128     if (*list != 0) {
129         int n;
130         for (n = 0; n < (*rows); ++n) {
131             if ((*list)[n] != 0)
132                 free((*list)[n]);
133         }
134         free(*list);
135         *list = 0;
136     }
137     *rows = 0;
138 }
139
140 /*
141  * Display a single row in the editing window:
142  * thisrow is the actual row number that's being displayed.
143  * show_row is the row number that's highlighted for edit.
144  * base_row is the first row number in the window
145  */
146 static bool
147 display_one(WINDOW *win,
148             char *text,
149             int thisrow,
150             int show_row,
151             int base_row,
152             int chr_offset)
153 {
154     bool result;
155
156     if (text != 0) {
157         dlg_show_string(win,
158                         text,
159                         chr_offset,
160                         ((thisrow == show_row)
161                          ? form_active_text_attr
162                          : form_text_attr),
163                         thisrow - base_row,
164                         0,
165                         getmaxx(win),
166                         FALSE,
167                         FALSE);
168         result = TRUE;
169     } else {
170         result = FALSE;
171     }
172     return result;
173 }
174
175 static void
176 display_all(WINDOW *win,
177             char **list,
178             int show_row,
179             int firstrow,
180             int lastrow,
181             int chr_offset)
182 {
183     int limit = getmaxy(win);
184     int row;
185
186     dlg_attr_clear(win, getmaxy(win), getmaxx(win), dialog_attr);
187     if (lastrow - firstrow >= limit)
188         lastrow = firstrow + limit;
189     for (row = firstrow; row < lastrow; ++row) {
190         if (!display_one(win, list[row],
191                          row, show_row, firstrow,
192                          (row == show_row) ? chr_offset : 0))
193             break;
194     }
195 }
196
197 static int
198 size_list(char **list)
199 {
200     int result = 0;
201
202     if (list != 0) {
203         while (*list++ != 0) {
204             ++result;
205         }
206     }
207     return result;
208 }
209
210 static bool
211 scroll_to(int pagesize, int rows, int *base_row, int *this_row, int target)
212 {
213     bool result = FALSE;
214
215     if (target < *base_row) {
216         if (target < 0) {
217             if (*base_row == 0 && *this_row == 0) {
218                 beep();
219             } else {
220                 *this_row = 0;
221                 *base_row = 0;
222                 result = TRUE;
223             }
224         } else {
225             *this_row = target;
226             *base_row = target;
227             result = TRUE;
228         }
229     } else if (target >= rows) {
230         if (*this_row < rows - 1) {
231             *this_row = rows - 1;
232             *base_row = rows - 1;
233             result = TRUE;
234         } else {
235             beep();
236         }
237     } else if (target >= *base_row + pagesize) {
238         *this_row = target;
239         *base_row = target;
240         result = TRUE;
241     } else {
242         *this_row = target;
243         result = FALSE;
244     }
245     if (pagesize < rows) {
246         if (*base_row + pagesize >= rows) {
247             *base_row = rows - pagesize;
248         }
249     } else {
250         *base_row = 0;
251     }
252     return result;
253 }
254
255 static int
256 col_to_chr_offset(const char *text, int col)
257 {
258     const int *cols = dlg_index_columns(text);
259     const int *indx = dlg_index_wchars(text);
260     bool found = FALSE;
261     int result = 0;
262     unsigned n;
263     unsigned len = (unsigned) dlg_count_wchars(text);
264
265     for (n = 0; n < len; ++n) {
266         if (cols[n] <= col && cols[n + 1] > col) {
267             result = indx[n];
268             found = TRUE;
269             break;
270         }
271     }
272     if (!found && len && cols[len] == col) {
273         result = indx[len];
274     }
275     return result;
276 }
277
278 #define Scroll_To(target) scroll_to(pagesize, listsize, &base_row, &thisrow, target)
279 #define SCROLL_TO(target) show_all = Scroll_To(target)
280
281 #define PREV_ROW (*list)[thisrow - 1]
282 #define THIS_ROW (*list)[thisrow]
283 #define NEXT_ROW (*list)[thisrow + 1]
284
285 #define UPDATE_COL(input) col_offset = dlg_edit_offset(input, chr_offset, box_width)
286
287 static int
288 widest_line(char **list)
289 {
290     int result = MAX_LEN;
291
292     if (list != 0) {
293         char *value;
294
295         while ((value = *list++) != 0) {
296             int check = (int) strlen(value);
297             if (check > result)
298                 result = check;
299         }
300     }
301     return result;
302 }
303
304 #define NAVIGATE_BINDINGS \
305         DLG_KEYS_DATA( DLGK_GRID_DOWN,  KEY_DOWN ), \
306         DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_RIGHT ), \
307         DLG_KEYS_DATA( DLGK_GRID_LEFT,  KEY_LEFT ), \
308         DLG_KEYS_DATA( DLGK_GRID_UP,    KEY_UP ), \
309         DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ), \
310         DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ), \
311         DLG_KEYS_DATA( DLGK_PAGE_FIRST, KEY_HOME ), \
312         DLG_KEYS_DATA( DLGK_PAGE_LAST,  KEY_END ), \
313         DLG_KEYS_DATA( DLGK_PAGE_LAST,  KEY_LL ), \
314         DLG_KEYS_DATA( DLGK_PAGE_NEXT,  KEY_NPAGE ), \
315         DLG_KEYS_DATA( DLGK_PAGE_NEXT,  DLGK_MOUSE(KEY_NPAGE) ), \
316         DLG_KEYS_DATA( DLGK_PAGE_PREV,  KEY_PPAGE ), \
317         DLG_KEYS_DATA( DLGK_PAGE_PREV,  DLGK_MOUSE(KEY_PPAGE) )
318 /*
319  * Display a dialog box for editing a copy of a file
320  */
321 int
322 dlg_editbox(const char *title,
323             char ***list,
324             int *rows,
325             int height,
326             int width)
327 {
328     /* *INDENT-OFF* */
329     static DLG_KEYS_BINDING binding[] = {
330         HELPKEY_BINDINGS,
331         ENTERKEY_BINDINGS,
332         NAVIGATE_BINDINGS,
333         TOGGLEKEY_BINDINGS,
334         END_KEYS_BINDING
335     };
336     static DLG_KEYS_BINDING binding2[] = {
337         INPUTSTR_BINDINGS,
338         HELPKEY_BINDINGS,
339         ENTERKEY_BINDINGS,
340         NAVIGATE_BINDINGS,
341         /* no TOGGLEKEY_BINDINGS, since that includes space... */
342         END_KEYS_BINDING
343     };
344     /* *INDENT-ON* */
345
346 #ifdef KEY_RESIZE
347     int old_height = height;
348     int old_width = width;
349 #endif
350     int x, y, box_y, box_x, box_height, box_width;
351     int show_buttons;
352     int thisrow, base_row, lastrow;
353     int goal_col = -1;
354     int col_offset = 0;
355     int chr_offset = 0;
356     int key, fkey, code;
357     int pagesize;
358     int listsize = size_list(*list);
359     int result = DLG_EXIT_UNKNOWN;
360     int state;
361     size_t max_len = (size_t) dlg_max_input(widest_line(*list));
362     char *buffer;
363     bool show_all, show_one;
364     bool first_trace = TRUE;
365     WINDOW *dialog;
366     WINDOW *editing;
367     DIALOG_VARS save_vars;
368     const char **buttons = dlg_ok_labels();
369     int mincols = (3 * COLS / 4);
370
371     DLG_TRACE(("# editbox args:\n"));
372     DLG_TRACE2S("title", title);
373     /* FIXME dump the rows & list */
374     DLG_TRACE2N("height", height);
375     DLG_TRACE2N("width", width);
376
377     dlg_save_vars(&save_vars);
378     dialog_vars.separate_output = TRUE;
379
380     dlg_does_output();
381
382     buffer = dlg_malloc(char, max_len + 1);
383     assert_ptr(buffer, "dlg_editbox");
384
385     thisrow = base_row = lastrow = 0;
386
387 #ifdef KEY_RESIZE
388   retry:
389 #endif
390     show_buttons = TRUE;
391     state = dialog_vars.default_button >= 0 ? dlg_default_button() : sTEXT;
392     fkey = 0;
393
394     dlg_button_layout(buttons, &mincols);
395     dlg_auto_size(title, "", &height, &width, 3 * LINES / 4, mincols);
396     dlg_print_size(height, width);
397     dlg_ctl_size(height, width);
398
399     x = dlg_box_x_ordinate(width);
400     y = dlg_box_y_ordinate(height);
401
402     dialog = dlg_new_window(height, width, y, x);
403     dlg_register_window(dialog, "editbox", binding);
404     dlg_register_buttons(dialog, "editbox", buttons);
405
406     dlg_mouse_setbase(x, y);
407
408     dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr);
409     dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr);
410     dlg_draw_title(dialog, title);
411
412     dlg_attrset(dialog, dialog_attr);
413
414     /* Draw the editing field in a box */
415     box_y = MARGIN + 0;
416     box_x = MARGIN + 1;
417     box_width = width - 2 - (2 * MARGIN);
418     box_height = height - (4 * MARGIN);
419
420     dlg_draw_box(dialog,
421                  box_y,
422                  box_x,
423                  box_height,
424                  box_width,
425                  border_attr, border2_attr);
426     dlg_mouse_mkbigregion(box_y + MARGIN,
427                           box_x + MARGIN,
428                           box_height - (2 * MARGIN),
429                           box_width - (2 * MARGIN),
430                           KEY_MAX, 1, 1, 3);
431     editing = dlg_sub_window(dialog,
432                              box_height - (2 * MARGIN),
433                              box_width - (2 * MARGIN),
434                              getbegy(dialog) + box_y + 1,
435                              getbegx(dialog) + box_x + 1);
436     dlg_register_window(editing, "editbox2", binding2);
437
438     show_all = TRUE;
439     show_one = FALSE;
440     pagesize = getmaxy(editing);
441
442     while (result == DLG_EXIT_UNKNOWN) {
443         bool was_mouse;
444         char *input;
445
446         if (show_all) {
447             display_all(editing, *list, thisrow, base_row, listsize, chr_offset);
448             display_one(editing, THIS_ROW,
449                         thisrow, thisrow, base_row, chr_offset);
450             show_all = FALSE;
451             show_one = TRUE;
452         } else {
453             if (thisrow != lastrow) {
454                 display_one(editing, (*list)[lastrow],
455                             lastrow, thisrow, base_row, 0);
456                 show_one = TRUE;
457             }
458         }
459         if (show_one) {
460             display_one(editing, THIS_ROW,
461                         thisrow, thisrow, base_row, chr_offset);
462             getyx(editing, y, x);
463             dlg_draw_scrollbar(dialog,
464                                base_row,
465                                base_row,
466                                base_row + pagesize,
467                                listsize,
468                                box_x,
469                                box_x + getmaxx(editing),
470                                box_y + 0,
471                                box_y + getmaxy(editing) + 1,
472                                border2_attr,
473                                border_attr);
474             wmove(editing, y, x);
475             show_one = FALSE;
476         }
477         lastrow = thisrow;
478         input = THIS_ROW;
479
480         /*
481          * The last field drawn determines where the cursor is shown:
482          */
483         if (show_buttons) {
484             show_buttons = FALSE;
485             UPDATE_COL(input);
486             if (state != sTEXT) {
487                 display_one(editing, input, thisrow,
488                             -1, base_row, 0);
489                 wrefresh(editing);
490             }
491             dlg_draw_buttons(dialog,
492                              height - 2,
493                              0,
494                              buttons,
495                              (state != sTEXT) ? state : 99,
496                              FALSE,
497                              width);
498             if (state == sTEXT) {
499                 display_one(editing, input, thisrow,
500                             thisrow, base_row, chr_offset);
501             }
502         }
503
504         if (first_trace) {
505             first_trace = FALSE;
506             dlg_trace_win(dialog);
507         }
508
509         key = dlg_mouse_wgetch((state == sTEXT) ? editing : dialog, &fkey);
510         if (key == ERR) {
511             result = DLG_EXIT_ERROR;
512             break;
513         } else if (key == ESC) {
514             result = DLG_EXIT_ESC;
515             break;
516         }
517         if (state != sTEXT) {
518             if (dlg_result_key(key, fkey, &result)) {
519                 if (!dlg_button_key(result, &code, &key, &fkey))
520                     break;
521             }
522         }
523
524         was_mouse = (fkey && is_DLGK_MOUSE(key));
525         if (was_mouse)
526             key -= M_EVENT;
527
528         /*
529          * Handle mouse clicks first, since we want to know if this is a
530          * button, or something that dlg_edit_string() should handle.
531          */
532         if (fkey
533             && was_mouse
534             && (code = dlg_ok_buttoncode(key)) >= 0) {
535             result = code;
536             continue;
537         }
538
539         if (was_mouse
540             && (key >= KEY_MAX)) {
541             int wide = getmaxx(editing);
542             int cell = key - KEY_MAX;
543             int check = (cell / wide) + base_row;
544             if (check < listsize) {
545                 thisrow = check;
546                 col_offset = (cell % wide);
547                 chr_offset = col_to_chr_offset(THIS_ROW, col_offset);
548                 show_one = TRUE;
549                 if (state != sTEXT) {
550                     state = sTEXT;
551                     show_buttons = TRUE;
552                 }
553             } else {
554                 beep();
555             }
556             continue;
557         } else if (was_mouse && key >= KEY_MIN) {
558             key = dlg_lookup_key(dialog, key, &fkey);
559         }
560
561         if (state == sTEXT) {   /* editing box selected */
562             int edit = 0;
563
564             /*
565              * Intercept scrolling keys that dlg_edit_string() does not
566              * understand.
567              */
568             if (fkey) {
569                 bool moved = TRUE;
570
571                 switch (key) {
572                 case DLGK_GRID_UP:
573                     SCROLL_TO(thisrow - 1);
574                     break;
575                 case DLGK_GRID_DOWN:
576                     SCROLL_TO(thisrow + 1);
577                     break;
578                 case DLGK_PAGE_FIRST:
579                     SCROLL_TO(0);
580                     break;
581                 case DLGK_PAGE_LAST:
582                     SCROLL_TO(listsize);
583                     break;
584                 case DLGK_PAGE_NEXT:
585                     SCROLL_TO(base_row + pagesize);
586                     break;
587                 case DLGK_PAGE_PREV:
588                     if (thisrow > base_row) {
589                         SCROLL_TO(base_row);
590                     } else {
591                         SCROLL_TO(base_row - pagesize);
592                     }
593                     break;
594                 case DLGK_DELETE_LEFT:
595                     if (chr_offset == 0) {
596                         if (thisrow == 0) {
597                             beep();
598                         } else {
599                             size_t len = (strlen(THIS_ROW) +
600                                           strlen(PREV_ROW) + 1);
601                             char *tmp = dlg_malloc(char, len);
602
603                             assert_ptr(tmp, "dlg_editbox");
604
605                             chr_offset = dlg_count_wchars(PREV_ROW);
606                             UPDATE_COL(PREV_ROW);
607                             goal_col = col_offset;
608
609                             sprintf(tmp, "%s%s", PREV_ROW, THIS_ROW);
610                             if (len > max_len)
611                                 tmp[max_len] = '\0';
612
613                             free(PREV_ROW);
614                             PREV_ROW = tmp;
615                             for (y = thisrow; y < listsize; ++y) {
616                                 (*list)[y] = (*list)[y + 1];
617                             }
618                             --listsize;
619                             --thisrow;
620                             (void) Scroll_To(thisrow);
621
622                             show_all = TRUE;
623                         }
624                     } else {
625                         /* dlg_edit_string() can handle this case */
626                         moved = FALSE;
627                     }
628                     break;
629                 default:
630                     moved = FALSE;
631                     break;
632                 }
633                 if (moved) {
634                     if (thisrow != lastrow) {
635                         if (goal_col < 0)
636                             goal_col = col_offset;
637                         chr_offset = col_to_chr_offset(THIS_ROW, goal_col);
638                     } else {
639                         UPDATE_COL(THIS_ROW);
640                     }
641                     continue;
642                 }
643             }
644             strncpy(buffer, input, max_len - 1)[max_len - 1] = '\0';
645             edit = dlg_edit_string(buffer, &chr_offset, key, fkey, FALSE);
646
647             if (edit) {
648                 goal_col = UPDATE_COL(input);
649                 if (strcmp(input, buffer)) {
650                     free(input);
651                     THIS_ROW = dlg_strclone(buffer);
652                     input = THIS_ROW;
653                 }
654                 display_one(editing, input, thisrow,
655                             thisrow, base_row, chr_offset);
656                 continue;
657             }
658         }
659
660         /* handle non-functionkeys */
661         if (!fkey && (code = dlg_char_to_button(key, buttons)) >= 0) {
662             dlg_del_window(dialog);
663             result = dlg_ok_buttoncode(code);
664             continue;
665         }
666
667         /* handle functionkeys */
668         if (fkey) {
669             switch (key) {
670             case DLGK_GRID_UP:
671             case DLGK_GRID_LEFT:
672             case DLGK_FIELD_PREV:
673                 show_buttons = TRUE;
674                 state = dlg_prev_ok_buttonindex(state, sTEXT);
675                 break;
676             case DLGK_GRID_RIGHT:
677             case DLGK_GRID_DOWN:
678             case DLGK_FIELD_NEXT:
679                 show_buttons = TRUE;
680                 state = dlg_next_ok_buttonindex(state, sTEXT);
681                 break;
682             case DLGK_ENTER:
683                 if (state == sTEXT) {
684                     const int *indx = dlg_index_wchars(THIS_ROW);
685                     int split = indx[chr_offset];
686                     char *tmp = dlg_strclone(THIS_ROW + split);
687
688                     assert_ptr(tmp, "dlg_editbox");
689                     grow_list(list, rows, listsize + 1);
690                     ++listsize;
691                     for (y = listsize; y > thisrow; --y) {
692                         (*list)[y] = (*list)[y - 1];
693                     }
694                     THIS_ROW[split] = '\0';
695                     ++thisrow;
696                     chr_offset = 0;
697                     col_offset = 0;
698                     THIS_ROW = tmp;
699                     (void) Scroll_To(thisrow);
700                     show_all = TRUE;
701                 } else {
702                     result = dlg_ok_buttoncode(state);
703                 }
704                 break;
705 #ifdef KEY_RESIZE
706             case KEY_RESIZE:
707                 dlg_will_resize(dialog);
708                 /* reset data */
709                 height = old_height;
710                 width = old_width;
711                 /* repaint */
712                 dlg_del_window(editing);
713                 dlg_unregister_window(editing);
714                 _dlg_resize_cleanup(dialog);
715                 goto retry;
716 #endif
717             case DLGK_TOGGLE:
718                 if (state != sTEXT) {
719                     result = dlg_ok_buttoncode(state);
720                 } else {
721                     beep();
722                 }
723                 break;
724             default:
725                 beep();
726                 break;
727             }
728         } else if (key > 0) {
729             beep();
730         }
731     }
732
733     dlg_unregister_window(editing);
734     dlg_del_window(editing);
735     dlg_del_window(dialog);
736     dlg_mouse_free_regions();
737
738     /*
739      * The caller's copy of the (*list)[] array has been updated, but for
740      * consistency with the other widgets, we put the "real" result in
741      * the output buffer.
742      */
743     if (result == DLG_EXIT_OK) {
744         int n;
745         for (n = 0; n < listsize; ++n) {
746             dlg_add_result((*list)[n]);
747             dlg_add_separator();
748         }
749         dlg_add_last_key(-1);
750     }
751     free(buffer);
752     dlg_restore_vars(&save_vars);
753     return result;
754 }
755
756 int
757 dialog_editbox(const char *title, const char *file, int height, int width)
758 {
759     int result;
760     char **list;
761     int rows;
762
763     load_list(file, &list, &rows);
764     result = dlg_editbox(title, &list, &rows, height, width);
765     free_list(&list, &rows);
766     return result;
767 }