5d068582779eb6c23671fc477220a37394a03d68
[metze/wireshark/wip.git] / epan / print_stream.c
1 /* print_stream.c
2  * Routines for print streams.
3  *
4  * Gilbert Ramirez <gram@alumni.rice.edu>
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * SPDX-License-Identifier: GPL-2.0-or-later
11  */
12
13 #include "config.h"
14
15 #include <stdio.h>
16 #include <string.h>
17
18 #ifdef _WIN32
19 #include <windows.h>
20 #endif
21
22 #include <glib.h>
23
24 #include <epan/print_stream.h>
25
26 #include <epan/ps.h>
27
28 #include <wsutil/file_util.h>
29
30 #define TERM_SGR_RESET "\x1B[0m"  /* SGR - reset */
31 #define TERM_CSI_EL    "\x1B[K"   /* EL - Erase in Line (to end of line) */
32
33 typedef struct {
34     gboolean  to_file;
35     FILE     *fh;
36 } output_text;
37
38 static void
39 print_color_escape(FILE *fh, const color_t *fg, const color_t *bg)
40 {
41 #ifdef _WIN32
42     /* default to white foreground, black background */
43     WORD win_fg_color = FOREGROUND_RED|FOREGROUND_BLUE|FOREGROUND_GREEN;
44     WORD win_bg_color = 0;
45
46     /* The classic Windows Console offers 1-bit color, so you can't set
47      * the red, green, or blue intensities, you can only set
48      * "{foreground, background} contains {red, green, blue}". So
49      * include red, green or blue if the numeric intensity is high
50      * enough.
51      *
52      * The console in Windows 10 version 1511 (TH2), build 10586, and later
53      * supports SGR escape sequences:
54      *
55      *  http://www.nivot.org/blog/post/2016/02/04/Windows-10-TH2-(v1511)-Console-Host-Enhancements
56      *
57      * but only supports 16 colors.  The "undocumented" 0x04 bit to which
58      * they refer is documented in the current version of the SetConsoleMode()
59      * documentation:
60      *
61      *  https://docs.microsoft.com/en-us/windows/console/setconsolemode
62      *
63      * as ENABLE_VIRTUAL_TERMINAL_PROCESSING, saying
64      *
65      *  When writing with WriteFile or WriteConsole, characters are parsed
66      *  for VT100 and similar control character sequences that control cursor
67      *  movement, color/font mode, and other operations that can also be
68      *  performed via the existing Console APIs. For more information, see
69      *  Console Virtual Terminal Sequences.
70      *
71      * Console Virtual Terminal Sequences:
72      *
73      *  https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
74      *
75      * documents all the escape sequences the Console supports.
76      *
77      * The console in Windows 10 builds 14931 (a preview version of Windows 10
78      * version 1703) and later supports SGR RGB sequences:
79      *
80      *  https://blogs.msdn.microsoft.com/commandline/2016/09/22/24-bit-color-in-the-windows-console/
81      *
82      * We might want to print those instead depending on the version of
83      * Windows or just remove the SetConsoleTextAttribute calls and only
84      * print SGR sequences if they are supported.
85      */
86     if (fg) {
87         if (((fg->red >> 8) & 0xff) >= 0x80)
88         {
89             win_fg_color |= FOREGROUND_RED;
90         }
91         else
92         {
93             win_fg_color &= (~FOREGROUND_RED);
94         }
95         if (((fg->green >> 8) & 0xff) >= 0x80)
96         {
97             win_fg_color |= FOREGROUND_GREEN;
98         }
99         else
100         {
101             win_fg_color &= (~FOREGROUND_GREEN);
102         }
103         if (((fg->blue >> 8) & 0xff) >= 0x80)
104         {
105             win_fg_color |= FOREGROUND_BLUE;
106         }
107         else
108         {
109             win_fg_color &= (~FOREGROUND_BLUE);
110         }
111     }
112
113     if (bg) {
114         if (((bg->red >> 8) & 0xff) >= 0x80)
115         {
116             win_bg_color |= BACKGROUND_RED;
117         }
118         else
119         {
120             win_bg_color &= (~BACKGROUND_RED);
121         }
122         if (((bg->green >> 8) & 0xff) >= 0x80)
123         {
124             win_bg_color |= BACKGROUND_GREEN;
125         }
126         else
127         {
128             win_bg_color &= (~BACKGROUND_GREEN);
129         }
130         if (((bg->blue >> 8) & 0xff) >= 0x80)
131         {
132             win_bg_color |= BACKGROUND_BLUE;
133         }
134         else
135         {
136             win_bg_color &= (~BACKGROUND_BLUE);
137         }
138     }
139
140     SetConsoleTextAttribute((HANDLE)_get_osfhandle(_fileno(fh)), win_fg_color|win_bg_color);
141 #else
142     /*
143      * UN*X.
144      *
145      * Use the "select character foreground colour" and "select character
146      * background colour" options to the Select Graphic Rendition control
147      * sequence; those are reserved in ECMA-48, and are specified in ISO
148      * standard 8613-6/ITU-T Recommendation T.416, "Open Document Architecture
149      * (ODA) and Interchange Format: Chararcter Content Architectures",
150      * section 13.1.8 "Select Graphic Rendition (SGR)".  We use the
151      * "direct colour in RGB space" option, with a parameter value of 2.
152      *
153      * Those sequences are supported by some UN*X terminal emulators; some
154      * support either : or ; as a separator, others require a ;.
155      *
156      * For more than you ever wanted to know about all of this, see
157      *
158      *    https://gist.github.com/XVilka/8346728
159      *
160      * including the discussion following it.
161      *
162      * XXX - this isn't always treated correctly; macOS Terminal currently
163      * doesn't handle this correctly - it gives weird colors.  Sadly, as
164      * per various other discussions mentioned in the discussion cited above,
165      * there's nothing in terminfo to indicate the presence of 24-bit color
166      * support, so there's no good way to decide whether to use this or not.
167      *
168      * XXX - fall back on 8-color or 256-color support if we can somehow
169      * determine that 24-bit color support isn't available but 8-color or
170      * 256-color support is?
171      */
172     if (fg) {
173         fprintf(fh, "\x1B[38;2;%u;%u;%um",
174                 (fg->red   >> 8) & 0xff,
175                 (fg->green >> 8) & 0xff,
176                 (fg->blue  >> 8) & 0xff);
177     }
178
179     if (bg) {
180         fprintf(fh, "\x1B[48;2;%u;%u;%um",
181                 (bg->red   >> 8) & 0xff,
182                 (bg->green >> 8) & 0xff,
183                 (bg->blue  >> 8) & 0xff);
184     }
185 #endif
186 }
187
188 static void
189 print_color_eol(print_stream_t *self)
190 {
191     output_text *output = (output_text *)self->data;
192     FILE *fh = output->fh;
193 #ifdef _WIN32
194     SetConsoleTextAttribute((HANDLE)_get_osfhandle(_fileno(fh)), self->csb_attrs);
195     fprintf(fh, "\n");
196 #else // UN*X
197
198     /*
199      * Emit CSI EL to extend current background color all the way to EOL,
200      * otherwise we get a ragged right edge of color wherever the newline
201      * occurs.  It's not perfect in every terminal emulator, but it generally
202      * works.
203      */
204     fprintf(fh, "%s\n%s", TERM_CSI_EL, TERM_SGR_RESET);
205 #endif
206 }
207
208 static FILE *
209 open_print_dest(gboolean to_file, const char *dest)
210 {
211     FILE *fh;
212
213     /* Open the file or command for output */
214     if (to_file)
215         fh = ws_fopen(dest, "w");
216     else
217         fh = popen(dest, "w");
218
219     return fh;
220 }
221
222 static gboolean
223 close_print_dest(gboolean to_file, FILE *fh)
224 {
225     /* Close the file or command */
226     if (to_file)
227         return (fclose(fh) == 0);
228     else
229         return (pclose(fh) == 0);
230 }
231
232 /* Some formats need stuff at the beginning of the output */
233 gboolean
234 print_preamble(print_stream_t *self, gchar *filename, const char *version_string)
235 {
236     return self->ops->print_preamble ? (self->ops->print_preamble)(self, filename, version_string) : TRUE;
237 }
238
239 gboolean
240 print_line(print_stream_t *self, int indent, const char *line)
241 {
242     return (self->ops->print_line)(self, indent, line);
243 }
244
245 gboolean
246 print_line_color(print_stream_t *self, int indent, const char *line, const color_t *fg, const color_t *bg)
247 {
248     if (self->ops->print_line_color)
249         return (self->ops->print_line_color)(self, indent, line, fg, bg);
250     else
251         return (self->ops->print_line)(self, indent, line);
252 }
253
254 /* Insert bookmark */
255 gboolean
256 print_bookmark(print_stream_t *self, const gchar *name, const gchar *title)
257 {
258     return self->ops->print_bookmark ? (self->ops->print_bookmark)(self, name, title) : TRUE;
259 }
260
261 gboolean
262 new_page(print_stream_t *self)
263 {
264     return self->ops->new_page ? (self->ops->new_page)(self) : TRUE;
265 }
266
267 /* Some formats need stuff at the end of the output */
268 gboolean
269 print_finale(print_stream_t *self)
270 {
271     return self->ops->print_finale ? (self->ops->print_finale)(self) : TRUE;
272 }
273
274 gboolean
275 destroy_print_stream(print_stream_t *self)
276 {
277     return (self && self->ops && self->ops->destroy) ? (self->ops->destroy)(self) : TRUE;
278 }
279
280 #define MAX_INDENT    160
281
282 /* returns TRUE if the print succeeded, FALSE if there was an error */
283 static gboolean
284 print_line_color_text(print_stream_t *self, int indent, const char *line, const color_t *fg, const color_t *bg)
285 {
286     static char spaces[MAX_INDENT];
287     size_t ret;
288     output_text *output = (output_text *)self->data;
289     unsigned int num_spaces;
290     gboolean emit_color = self->isatty && (fg != NULL || bg != NULL);
291
292     /* should be space, if NUL -> initialize */
293     if (!spaces[0])
294         memset(spaces, ' ', sizeof(spaces));
295
296     if (emit_color) {
297         print_color_escape(output->fh, fg, bg);
298         if (ferror(output->fh))
299             return FALSE;
300     }
301
302     /* Prepare the tabs for printing, depending on tree level */
303     num_spaces = indent * 4;
304     if (num_spaces > MAX_INDENT)
305         num_spaces = MAX_INDENT;
306
307     ret = fwrite(spaces, 1, num_spaces, output->fh);
308     if (ret == num_spaces) {
309         gchar *tty_out = NULL;
310
311         if (self->isatty && self->to_codeset) {
312             /* XXX Allocating a fresh buffer every line probably isn't the
313              * most efficient way to do this. However, this has the side
314              * effect of scrubbing invalid output.
315              */
316             tty_out = g_convert_with_fallback(line, -1, self->to_codeset, "UTF-8", "?", NULL, NULL, NULL);
317         }
318
319         if (tty_out) {
320 #ifdef _WIN32
321             DWORD out_len = (DWORD) wcslen((wchar_t *) tty_out);
322             WriteConsoleW((HANDLE)_get_osfhandle(_fileno(output->fh)), tty_out, out_len, &out_len, NULL);
323 #else
324             fputs(tty_out, output->fh);
325 #endif
326             g_free(tty_out);
327         } else {
328             fputs(line, output->fh);
329         }
330
331         if (emit_color)
332             print_color_eol(self);
333         else
334             putc('\n', output->fh);
335     }
336
337     return !ferror(output->fh);
338 }
339
340 static gboolean
341 print_line_text(print_stream_t *self, int indent, const char *line)
342 {
343     return print_line_color_text(self, indent, line, NULL, NULL);
344 }
345
346 static gboolean
347 new_page_text(print_stream_t *self)
348 {
349     output_text *output = (output_text *)self->data;
350
351     fputs("\f", output->fh);
352     return !ferror(output->fh);
353 }
354
355 static gboolean
356 destroy_text(print_stream_t *self)
357 {
358     output_text *output = (output_text *)self->data;
359     gboolean     ret;
360
361     ret = close_print_dest(output->to_file, output->fh);
362     g_free(output);
363     g_free(self);
364     return ret;
365 }
366
367 static const print_stream_ops_t print_text_ops = {
368     NULL,            /* preamble */
369     print_line_text,
370     NULL,            /* bookmark */
371     new_page_text,
372     NULL,            /* finale */
373     destroy_text,
374     print_line_color_text,
375 };
376
377 static print_stream_t *
378 print_stream_text_alloc(gboolean to_file, FILE *fh)
379 {
380     print_stream_t *stream;
381     output_text    *output;
382 #ifndef _WIN32
383     const gchar *charset;
384     gboolean is_utf8;
385 #endif
386
387     output          = (output_text *)g_malloc(sizeof *output);
388     output->to_file = to_file;
389     output->fh      = fh;
390     stream          = (print_stream_t *)g_malloc0(sizeof (print_stream_t));
391     stream->ops     = &print_text_ops;
392     stream->isatty  = ws_isatty(ws_fileno(fh));
393     stream->data    = output;
394
395 #ifndef _WIN32
396     /* Is there a more reliable way to do this? */
397     is_utf8 = g_get_charset(&charset);
398     if (!is_utf8) {
399         stream->to_codeset = charset;
400     }
401 #else
402     CONSOLE_SCREEN_BUFFER_INFO csb_info;
403     GetConsoleScreenBufferInfo((HANDLE)_get_osfhandle(_fileno(fh)), &csb_info);
404     stream->csb_attrs = csb_info.wAttributes;
405
406     stream->to_codeset = "UTF-16LE";
407 #endif
408
409     return stream;
410 }
411
412 print_stream_t *
413 print_stream_text_new(gboolean to_file, const char *dest)
414 {
415     FILE *fh;
416
417     fh = open_print_dest(to_file, dest);
418     if (fh == NULL)
419         return NULL;
420
421     return print_stream_text_alloc(to_file, fh);
422 }
423
424 print_stream_t *
425 print_stream_text_stdio_new(FILE *fh)
426 {
427     return print_stream_text_alloc(TRUE, fh);
428 }
429
430 typedef struct {
431     gboolean  to_file;
432     FILE     *fh;
433 } output_ps;
434
435 #define MAX_PS_LINE_LENGTH 256
436
437 static
438 void ps_clean_string(char *out, const char *in, int outbuf_size)
439 {
440     int  rd, wr;
441     char c;
442
443     if (in == NULL) {
444         out[0] = '\0';
445         return;
446     }
447
448     for (rd = 0, wr = 0 ; wr < outbuf_size; rd++, wr++ ) {
449         c = in[rd];
450         switch (c) {
451         case '(':
452         case ')':
453         case '\\':
454             out[wr] = '\\';
455             out[++wr] = c;
456             break;
457
458         default:
459             out[wr] = c;
460             break;
461         }
462
463         if (c == 0) {
464             break;
465         }
466     }
467 }
468
469 static gboolean
470 print_preamble_ps(print_stream_t *self, gchar *filename, const char *version_string)
471 {
472     output_ps *output = (output_ps *)self->data;
473     char       psbuffer[MAX_PS_LINE_LENGTH]; /* static sized buffer! */
474
475     print_ps_preamble(output->fh);
476
477     fputs("%% the page title\n", output->fh);
478     ps_clean_string(psbuffer, filename, MAX_PS_LINE_LENGTH);
479     fprintf(output->fh, "/ws_pagetitle (%s - Wireshark %s) def\n", psbuffer, version_string);
480     fputs("\n", output->fh);
481     return !ferror(output->fh);
482 }
483
484 static gboolean
485 print_line_ps(print_stream_t *self, int indent, const char *line)
486 {
487     output_ps *output = (output_ps *)self->data;
488     char       psbuffer[MAX_PS_LINE_LENGTH]; /* static sized buffer! */
489
490     ps_clean_string(psbuffer, line, MAX_PS_LINE_LENGTH);
491     fprintf(output->fh, "%d (%s) putline\n", indent, psbuffer);
492     return !ferror(output->fh);
493 }
494
495 static gboolean
496 print_bookmark_ps(print_stream_t *self, const gchar *name, const gchar *title)
497 {
498     output_ps *output = (output_ps *)self->data;
499     char       psbuffer[MAX_PS_LINE_LENGTH]; /* static sized buffer! */
500
501     /*
502      * See the Adobe "pdfmark reference":
503      *
504      *  http://partners.adobe.com/asn/acrobat/docs/pdfmark.pdf
505      *
506      * The pdfmark stuff tells code that turns PostScript into PDF
507      * things that it should do.
508      *
509      * The /OUT stuff creates a bookmark that goes to the
510      * destination with "name" as the name and "title" as the title.
511      *
512      * The "/DEST" creates the destination.
513      */
514     ps_clean_string(psbuffer, title, MAX_PS_LINE_LENGTH);
515     fprintf(output->fh, "[/Dest /%s /Title (%s)   /OUT pdfmark\n", name,
516           psbuffer);
517     fputs("[/View [/XYZ -4 currentpoint matrix currentmatrix matrix defaultmatrix\n",
518           output->fh);
519     fputs("matrix invertmatrix matrix concatmatrix transform exch pop 20 add null]\n",
520           output->fh);
521     fprintf(output->fh, "/Dest /%s /DEST pdfmark\n", name);
522     return !ferror(output->fh);
523 }
524
525 static gboolean
526 new_page_ps(print_stream_t *self)
527 {
528     output_ps *output = (output_ps *)self->data;
529
530     fputs("formfeed\n", output->fh);
531     return !ferror(output->fh);
532 }
533
534 static gboolean
535 print_finale_ps(print_stream_t *self)
536 {
537     output_ps *output = (output_ps *)self->data;
538
539     print_ps_finale(output->fh);
540     return !ferror(output->fh);
541 }
542
543 static gboolean
544 destroy_ps(print_stream_t *self)
545 {
546     output_ps *output = (output_ps *)self->data;
547     gboolean   ret;
548
549     ret = close_print_dest(output->to_file, output->fh);
550     g_free(output);
551     g_free(self);
552     return ret;
553 }
554
555 static const print_stream_ops_t print_ps_ops = {
556     print_preamble_ps,
557     print_line_ps,
558     print_bookmark_ps,
559     new_page_ps,
560     print_finale_ps,
561     destroy_ps,
562     NULL, /* print_line_color */
563 };
564
565 static print_stream_t *
566 print_stream_ps_alloc(gboolean to_file, FILE *fh)
567 {
568     print_stream_t *stream;
569     output_ps      *output;
570
571     output          = (output_ps *)g_malloc(sizeof *output);
572     output->to_file = to_file;
573     output->fh      = fh;
574     stream          = (print_stream_t *)g_malloc(sizeof (print_stream_t));
575     stream->ops     = &print_ps_ops;
576     stream->data    = output;
577
578     return stream;
579 }
580
581 print_stream_t *
582 print_stream_ps_new(gboolean to_file, const char *dest)
583 {
584     FILE *fh;
585
586     fh = open_print_dest(to_file, dest);
587     if (fh == NULL)
588         return NULL;
589
590     return print_stream_ps_alloc(to_file, fh);
591 }
592
593 print_stream_t *
594 print_stream_ps_stdio_new(FILE *fh)
595 {
596     return print_stream_ps_alloc(TRUE, fh);
597 }
598
599 /*
600  * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
601  *
602  * Local variables:
603  * c-basic-offset: 4
604  * tab-width: 8
605  * indent-tabs-mode: nil
606  * End:
607  *
608  * vi: set shiftwidth=4 tabstop=8 expandtab:
609  * :indentSize=4:tabSize=8:noTabs=true:
610  */