1 /*
2    Internal file viewer for the Midnight Commander
3    Function for hex view
4 
5    Copyright (C) 1994-2021
6    Free Software Foundation, Inc.
7 
8    Written by:
9    Miguel de Icaza, 1994, 1995, 1998
10    Janne Kukonlehto, 1994, 1995
11    Jakub Jelinek, 1995
12    Joseph M. Hinkle, 1996
13    Norbert Warmuth, 1997
14    Pavel Machek, 1998
15    Roland Illig <roland.illig@gmx.de>, 2004, 2005
16    Slava Zanko <slavazanko@google.com>, 2009, 2013
17    Andrew Borodin <aborodin@vmail.ru>, 2009
18    Ilia Maslakov <il.smind@gmail.com>, 2009
19 
20    This file is part of the Midnight Commander.
21 
22    The Midnight Commander is free software: you can redistribute it
23    and/or modify it under the terms of the GNU General Public License as
24    published by the Free Software Foundation, either version 3 of the License,
25    or (at your option) any later version.
26 
27    The Midnight Commander is distributed in the hope that it will be useful,
28    but WITHOUT ANY WARRANTY; without even the implied warranty of
29    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30    GNU General Public License for more details.
31 
32    You should have received a copy of the GNU General Public License
33    along with this program.  If not, see <http://www.gnu.org/licenses/>.
34  */
35 
36 #include <config.h>
37 
38 #include <errno.h>
39 #include <inttypes.h>           /* uintmax_t */
40 
41 #include "lib/global.h"
42 #include "lib/tty/tty.h"
43 #include "lib/skin.h"
44 #include "lib/vfs/vfs.h"
45 #include "lib/lock.h"           /* lock_file() and unlock_file() */
46 #include "lib/util.h"
47 #include "lib/widget.h"
48 #ifdef HAVE_CHARSET
49 #include "lib/charsets.h"
50 #endif
51 
52 #include "internal.h"
53 
54 /*** global variables ****************************************************************************/
55 
56 /*** file scope macro definitions ****************************************************************/
57 
58 /*** file scope type declarations ****************************************************************/
59 
60 typedef enum
61 {
62     MARK_NORMAL,
63     MARK_SELECTED,
64     MARK_CURSOR,
65     MARK_CHANGED
66 } mark_t;
67 
68 /*** file scope variables ************************************************************************/
69 
70 static const char hex_char[] = "0123456789ABCDEF";
71 
72 /*** file scope functions ************************************************************************/
73 /* --------------------------------------------------------------------------------------------- */
74 
75 /* --------------------------------------------------------------------------------------------- */
76 /** Determine the state of the current byte.
77  *
78  * @param view viewer object
79  * @param from offset
80  * @param curr current node
81  */
82 
83 static mark_t
mcview_hex_calculate_boldflag(WView * view,off_t from,struct hexedit_change_node * curr,gboolean force_changed)84 mcview_hex_calculate_boldflag (WView * view, off_t from, struct hexedit_change_node *curr,
85                                gboolean force_changed)
86 {
87     return (from == view->hex_cursor) ? MARK_CURSOR
88         : ((curr != NULL && from == curr->offset) || force_changed) ? MARK_CHANGED
89         : (view->search_start <= from && from < view->search_end) ? MARK_SELECTED : MARK_NORMAL;
90 }
91 
92 /* --------------------------------------------------------------------------------------------- */
93 /*** public functions ****************************************************************************/
94 /* --------------------------------------------------------------------------------------------- */
95 
96 void
mcview_display_hex(WView * view)97 mcview_display_hex (WView * view)
98 {
99     const screen_dimen top = view->data_area.top;
100     const screen_dimen left = view->data_area.left;
101     const screen_dimen height = view->data_area.height;
102     const screen_dimen width = view->data_area.width;
103     const int ngroups = view->bytes_per_line / 4;
104     /* 8 characters are used for the file offset, and every hex group
105      * takes 13 characters. Starting at width of 80 columns, the groups
106      * are separated by an extra vertical line. Starting at width of 81,
107      * there is an extra space before the text column. There is always a
108      * mostly empty column on the right, to allow overflowing CJKs.
109      */
110     const screen_dimen text_start = 8 + 13 * ngroups +
111         ((width < 80) ? 0 : (width == 80) ? (ngroups - 1) : (ngroups - 1 + 1));
112 
113     int row;
114     off_t from;
115     mark_t boldflag_byte = MARK_NORMAL;
116     mark_t boldflag_char = MARK_NORMAL;
117     struct hexedit_change_node *curr = view->change_list;
118 #ifdef HAVE_CHARSET
119     int cont_bytes = 0;         /* number of continuation bytes remanining from current UTF-8 */
120     gboolean cjk_right = FALSE; /* whether the second byte of a CJK is to be processed */
121 #endif /* HAVE_CHARSET */
122     gboolean utf8_changed = FALSE;      /* whether any of the bytes in the UTF-8 were changed */
123 
124     char hex_buff[10];          /* A temporary buffer for sprintf and mvwaddstr */
125 
126     mcview_display_clean (view);
127 
128     /* Find the first displayable changed byte */
129     /* In UTF-8 mode, go back by 1 or maybe 2 lines to handle continuation bytes properly. */
130     from = view->dpy_start;
131     row = 0;
132 #ifdef HAVE_CHARSET
133     if (view->utf8)
134     {
135         if (from >= view->bytes_per_line)
136         {
137             row--;
138             from -= view->bytes_per_line;
139         }
140         if (view->bytes_per_line == 4 && from >= view->bytes_per_line)
141         {
142             row--;
143             from -= view->bytes_per_line;
144         }
145     }
146 #endif /* HAVE_CHARSET */
147     while (curr && (curr->offset < from))
148     {
149         curr = curr->next;
150     }
151 
152     for (; mcview_get_byte (view, from, NULL) && row < (int) height; row++)
153     {
154         screen_dimen col = 0;
155         int bytes;              /* Number of bytes already printed on the line */
156 
157         /* Print the hex offset */
158         if (row >= 0)
159         {
160             size_t i;
161 
162             g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
163             widget_gotoyx (view, top + row, left);
164             tty_setcolor (VIEW_BOLD_COLOR);
165             for (i = 0; col < width && hex_buff[i] != '\0'; col++, i++)
166                 tty_print_char (hex_buff[i]);
167             tty_setcolor (VIEW_NORMAL_COLOR);
168         }
169 
170         for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++)
171         {
172             int c;
173 #ifdef HAVE_CHARSET
174             int ch = 0;
175 
176             if (view->utf8)
177             {
178                 struct hexedit_change_node *corr = curr;
179 
180                 if (cont_bytes != 0)
181                 {
182                     /* UTF-8 continuation bytes, print a space (with proper attributes)... */
183                     cont_bytes--;
184                     ch = ' ';
185                     if (cjk_right)
186                     {
187                         /* ... except when it'd wipe out the right half of a CJK, then print nothing */
188                         cjk_right = FALSE;
189                         ch = -1;
190                     }
191                 }
192                 else
193                 {
194                     int j;
195                     gchar utf8buf[UTF8_CHAR_LEN + 1];
196                     int res;
197                     int first_changed = -1;
198 
199                     for (j = 0; j < UTF8_CHAR_LEN; j++)
200                     {
201                         if (mcview_get_byte (view, from + j, &res))
202                             utf8buf[j] = res;
203                         else
204                         {
205                             utf8buf[j] = '\0';
206                             break;
207                         }
208                         if (curr != NULL && from + j == curr->offset)
209                         {
210                             utf8buf[j] = curr->value;
211                             if (first_changed == -1)
212                                 first_changed = j;
213                         }
214                         if (curr != NULL && from + j >= curr->offset)
215                             curr = curr->next;
216                     }
217                     utf8buf[UTF8_CHAR_LEN] = '\0';
218 
219                     /* Determine the state of the current multibyte char */
220                     ch = g_utf8_get_char_validated (utf8buf, -1);
221                     if (ch == -1 || ch == -2)
222                     {
223                         ch = '.';
224                     }
225                     else
226                     {
227                         gchar *next_ch;
228 
229                         next_ch = g_utf8_next_char (utf8buf);
230                         cont_bytes = next_ch - utf8buf - 1;
231                         if (g_unichar_iswide (ch))
232                             cjk_right = TRUE;
233                     }
234 
235                     utf8_changed = (first_changed >= 0 && first_changed <= cont_bytes);
236                     curr = corr;
237                 }
238             }
239 #endif /* HAVE_CHARSET */
240 
241             /* For negative rows, the only thing we care about is overflowing
242              * UTF-8 continuation bytes which were handled above. */
243             if (row < 0)
244             {
245                 if (curr != NULL && from == curr->offset)
246                     curr = curr->next;
247                 continue;
248             }
249 
250             if (!mcview_get_byte (view, from, &c))
251                 break;
252 
253             /* Save the cursor position for mcview_place_cursor() */
254             if (from == view->hex_cursor && !view->hexview_in_text)
255             {
256                 view->cursor_row = row;
257                 view->cursor_col = col;
258             }
259 
260             /* Determine the state of the current byte */
261             boldflag_byte = mcview_hex_calculate_boldflag (view, from, curr, FALSE);
262             boldflag_char = mcview_hex_calculate_boldflag (view, from, curr, utf8_changed);
263 
264             /* Determine the value of the current byte */
265             if (curr != NULL && from == curr->offset)
266             {
267                 c = curr->value;
268                 curr = curr->next;
269             }
270 
271             /* Select the color for the hex number */
272             tty_setcolor (boldflag_byte == MARK_NORMAL ? VIEW_NORMAL_COLOR :
273                           boldflag_byte == MARK_SELECTED ? VIEW_BOLD_COLOR :
274                           boldflag_byte == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
275                           /* boldflag_byte == MARK_CURSOR */
276                           view->hexview_in_text ? VIEW_SELECTED_COLOR : VIEW_UNDERLINED_COLOR);
277 
278             /* Print the hex number */
279             widget_gotoyx (view, top + row, left + col);
280             if (col < width)
281             {
282                 tty_print_char (hex_char[c / 16]);
283                 col += 1;
284             }
285             if (col < width)
286             {
287                 tty_print_char (hex_char[c % 16]);
288                 col += 1;
289             }
290 
291             /* Print the separator */
292             tty_setcolor (VIEW_NORMAL_COLOR);
293             if (bytes != view->bytes_per_line - 1)
294             {
295                 if (col < width)
296                 {
297                     tty_print_char (' ');
298                     col += 1;
299                 }
300 
301                 /* After every four bytes, print a group separator */
302                 if (bytes % 4 == 3)
303                 {
304                     if (view->data_area.width >= 80 && col < width)
305                     {
306                         tty_print_one_vline (TRUE);
307                         col += 1;
308                     }
309                     if (col < width)
310                     {
311                         tty_print_char (' ');
312                         col += 1;
313                     }
314                 }
315             }
316 
317             /* Select the color for the character; this differs from the
318              * hex color when boldflag == MARK_CURSOR */
319             tty_setcolor (boldflag_char == MARK_NORMAL ? VIEW_NORMAL_COLOR :
320                           boldflag_char == MARK_SELECTED ? VIEW_BOLD_COLOR :
321                           boldflag_char == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
322                           /* boldflag_char == MARK_CURSOR */
323                           view->hexview_in_text ? VIEW_SELECTED_COLOR : MARKED_SELECTED_COLOR);
324 
325 
326 #ifdef HAVE_CHARSET
327             if (mc_global.utf8_display)
328             {
329                 if (!view->utf8)
330                 {
331                     c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
332                 }
333                 if (!g_unichar_isprint (c))
334                     c = '.';
335             }
336             else if (view->utf8)
337                 ch = convert_from_utf_to_current_c (ch, view->converter);
338             else
339 #endif
340             {
341 #ifdef HAVE_CHARSET
342                 c = convert_to_display_c (c);
343 #endif
344 
345                 if (!is_printable (c))
346                     c = '.';
347             }
348 
349             /* Print corresponding character on the text side */
350             if (text_start + bytes < width)
351             {
352                 widget_gotoyx (view, top + row, left + text_start + bytes);
353 #ifdef HAVE_CHARSET
354                 if (view->utf8)
355                     tty_print_anychar (ch);
356                 else
357 #endif
358                     tty_print_char (c);
359             }
360 
361             /* Save the cursor position for mcview_place_cursor() */
362             if (from == view->hex_cursor && view->hexview_in_text)
363             {
364                 view->cursor_row = row;
365                 view->cursor_col = text_start + bytes;
366             }
367         }
368     }
369 
370     /* Be polite to the other functions */
371     tty_setcolor (VIEW_NORMAL_COLOR);
372 
373     mcview_place_cursor (view);
374     view->dpy_end = from;
375 }
376 
377 /* --------------------------------------------------------------------------------------------- */
378 
379 gboolean
mcview_hexedit_save_changes(WView * view)380 mcview_hexedit_save_changes (WView * view)
381 {
382     int answer = 0;
383 
384     if (view->change_list == NULL)
385         return TRUE;
386 
387     while (answer == 0)
388     {
389         int fp;
390         char *text;
391         struct hexedit_change_node *curr, *next;
392 
393         g_assert (view->filename_vpath != NULL);
394 
395         fp = mc_open (view->filename_vpath, O_WRONLY);
396         if (fp != -1)
397         {
398             for (curr = view->change_list; curr != NULL; curr = next)
399             {
400                 next = curr->next;
401 
402                 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
403                     || mc_write (fp, &(curr->value), 1) != 1)
404                     goto save_error;
405 
406                 /* delete the saved item from the change list */
407                 view->change_list = next;
408                 view->dirty++;
409                 mcview_set_byte (view, curr->offset, curr->value);
410                 g_free (curr);
411             }
412 
413             view->change_list = NULL;
414 
415             if (view->locked)
416                 view->locked = unlock_file (view->filename_vpath);
417 
418             if (mc_close (fp) == -1)
419                 message (D_ERROR, _("Save file"),
420                          _("Error while closing the file:\n%s\n"
421                            "Data may have been written or not"), unix_error_string (errno));
422 
423             view->dirty++;
424             return TRUE;
425         }
426 
427       save_error:
428         text = g_strdup_printf (_("Cannot save file:\n%s"), unix_error_string (errno));
429         (void) mc_close (fp);
430 
431         answer = query_dialog (_("Save file"), text, D_ERROR, 2, _("&Retry"), _("&Cancel"));
432         g_free (text);
433     }
434 
435     return FALSE;
436 }
437 
438 /* --------------------------------------------------------------------------------------------- */
439 
440 void
mcview_toggle_hexedit_mode(WView * view)441 mcview_toggle_hexedit_mode (WView * view)
442 {
443     view->hexedit_mode = !view->hexedit_mode;
444     view->dpy_bbar_dirty = TRUE;
445     view->dirty++;
446 }
447 
448 /* --------------------------------------------------------------------------------------------- */
449 
450 void
mcview_hexedit_free_change_list(WView * view)451 mcview_hexedit_free_change_list (WView * view)
452 {
453     struct hexedit_change_node *curr, *next;
454 
455     for (curr = view->change_list; curr != NULL; curr = next)
456     {
457         next = curr->next;
458         g_free (curr);
459     }
460     view->change_list = NULL;
461 
462     if (view->locked)
463         view->locked = unlock_file (view->filename_vpath);
464 
465     view->dirty++;
466 }
467 
468 /* --------------------------------------------------------------------------------------------- */
469 
470 void
mcview_enqueue_change(struct hexedit_change_node ** head,struct hexedit_change_node * node)471 mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
472 {
473     /* chnode always either points to the head of the list or
474      * to one of the ->next fields in the list. The value at
475      * this location will be overwritten with the new node.   */
476     struct hexedit_change_node **chnode = head;
477 
478     while (*chnode != NULL && (*chnode)->offset < node->offset)
479         chnode = &((*chnode)->next);
480 
481     node->next = *chnode;
482     *chnode = node;
483 }
484 
485 /* --------------------------------------------------------------------------------------------- */
486