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