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 
21 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
22 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
23 #endif /* ENABLE_VIRTUAL_TERMINAL_PROCESSING */
24 #else
25 #include <stdlib.h> /* for getenv() */
26 #include <unistd.h> /* for isatty() */
27 #endif
28 
29 #include <glib.h>
30 
31 #include <epan/print_stream.h>
32 
33 #include <epan/ps.h>
34 
35 #include <wsutil/file_util.h>
36 
37 #define TERM_SGR_RESET "\x1B[0m"  /* SGR - reset */
38 #define TERM_CSI_EL    "\x1B[K"   /* EL - Erase in Line (to end of line) */
39 
40 typedef enum {
41     COLOR_NONE,
42 #ifdef _WIN32
43     COLOR_CONSOLE,
44 #endif
45     COLOR_24BIT_ESCAPE
46 } color_type_t;
47 
48 typedef struct {
49     gboolean     to_file;
50     FILE        *fh;
51     gboolean     isatty;
52     const char  *to_codeset;
53     color_type_t color_type;
54 #ifdef _WIN32
55     WORD         csb_attrs;
56     DWORD        console_mode;
57 #endif
58 } output_text;
59 
60 #ifdef _WIN32
61 /*
62  * The classic Windows Console offers 1-bit color, so you can't set
63  * the red, green, or blue intensities, you can only set
64  * "{foreground, background} contains {red, green, blue}". So
65  * include red, green or blue if the numeric intensity is high
66  * enough.
67  */
68 static void
set_color_console(FILE * fh,const color_t * fg,const color_t * bg)69 set_color_console(FILE *fh, const color_t *fg, const color_t *bg)
70 {
71     /* default to white foreground, black background */
72     WORD win_fg_color = FOREGROUND_RED|FOREGROUND_BLUE|FOREGROUND_GREEN;
73     WORD win_bg_color = 0;
74 
75     if (fg) {
76         if (((fg->red >> 8) & 0xff) >= 0x80)
77         {
78             win_fg_color |= FOREGROUND_RED;
79         }
80         else
81         {
82             win_fg_color &= (~FOREGROUND_RED);
83         }
84         if (((fg->green >> 8) & 0xff) >= 0x80)
85         {
86             win_fg_color |= FOREGROUND_GREEN;
87         }
88         else
89         {
90             win_fg_color &= (~FOREGROUND_GREEN);
91         }
92         if (((fg->blue >> 8) & 0xff) >= 0x80)
93         {
94             win_fg_color |= FOREGROUND_BLUE;
95         }
96         else
97         {
98             win_fg_color &= (~FOREGROUND_BLUE);
99         }
100     }
101 
102     if (bg) {
103         if (((bg->red >> 8) & 0xff) >= 0x80)
104         {
105             win_bg_color |= BACKGROUND_RED;
106         }
107         else
108         {
109             win_bg_color &= (~BACKGROUND_RED);
110         }
111         if (((bg->green >> 8) & 0xff) >= 0x80)
112         {
113             win_bg_color |= BACKGROUND_GREEN;
114         }
115         else
116         {
117             win_bg_color &= (~BACKGROUND_GREEN);
118         }
119         if (((bg->blue >> 8) & 0xff) >= 0x80)
120         {
121             win_bg_color |= BACKGROUND_BLUE;
122         }
123         else
124         {
125             win_bg_color &= (~BACKGROUND_BLUE);
126         }
127     }
128 
129     SetConsoleTextAttribute((HANDLE)_get_osfhandle(_fileno(fh)), win_fg_color|win_bg_color);
130 }
131 #endif
132 
133 /*
134  * Use the SGR escape sequences to specify a 24-bit color.
135  */
136 static void
set_color_24bit_escape(FILE * fh,const color_t * fg,const color_t * bg)137 set_color_24bit_escape(FILE *fh, const color_t *fg, const color_t *bg)
138 {
139     /*
140      * Use the "select character foreground colour" and "select character
141      * background colour" options to the Select Graphic Rendition control
142      * sequence; those are reserved in ECMA-48, and are specified in ISO
143      * standard 8613-6/ITU-T Recommendation T.416, "Open Document Architecture
144      * (ODA) and Interchange Format: Chararcter Content Architectures",
145      * section 13.1.8 "Select Graphic Rendition (SGR)".  We use the
146      * "direct colour in RGB space" option, with a parameter value of 2.
147      *
148      * Those sequences are supported by some UN*X terminal emulators; some
149      * support either : or ; as a separator, others require a ;.
150      *
151      * For more than you ever wanted to know about all of this, see
152      *
153      *    https://github.com/termstandard/colors
154      *
155      * and
156      *
157      *    https://gist.github.com/XVilka/8346728
158      *
159      * including the discussion following it, and
160      *
161      *    https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
162      *
163      * They are also supported by versions of the Windows Console that
164      * allow setting the ENABLE_VIRTUAL_TERMINAL_PROCESSING mode; that
165      * mode tells the console to interpret escape sequences written
166      * to it.
167      */
168     if (fg) {
169         fprintf(fh, "\x1B[38;2;%u;%u;%um",
170                 (fg->red   >> 8) & 0xff,
171                 (fg->green >> 8) & 0xff,
172                 (fg->blue  >> 8) & 0xff);
173     }
174 
175     if (bg) {
176         fprintf(fh, "\x1B[48;2;%u;%u;%um",
177                 (bg->red   >> 8) & 0xff,
178                 (bg->green >> 8) & 0xff,
179                 (bg->blue  >> 8) & 0xff);
180     }
181 }
182 
183 #ifdef _WIN32
184 static void
do_color_eol_console(print_stream_t * self)185 do_color_eol_console(print_stream_t *self)
186 {
187     output_text *output = (output_text *)self->data;
188     FILE *fh = output->fh;
189 
190     SetConsoleTextAttribute((HANDLE)_get_osfhandle(_fileno(fh)), output->csb_attrs);
191     fprintf(fh, "\n");
192 }
193 #endif
194 
195 static void
do_color_eol_24bit_escape(print_stream_t * self)196 do_color_eol_24bit_escape(print_stream_t *self)
197 {
198     output_text *output = (output_text *)self->data;
199     FILE *fh = output->fh;
200 
201     /*
202      * Emit CSI EL to extend current background color all the way to EOL,
203      * otherwise we get a ragged right edge of color wherever the newline
204      * occurs.  It's not perfect in every terminal emulator, but it generally
205      * works.
206      */
207     fprintf(fh, "%s\n%s", TERM_CSI_EL, TERM_SGR_RESET);
208 }
209 
210 static FILE *
open_print_dest(gboolean to_file,const char * dest)211 open_print_dest(gboolean to_file, const char *dest)
212 {
213     FILE *fh;
214 
215     /* Open the file or command for output */
216     if (to_file)
217         fh = ws_fopen(dest, "w");
218     else
219         fh = popen(dest, "w");
220 
221     return fh;
222 }
223 
224 static gboolean
close_print_dest(gboolean to_file,FILE * fh)225 close_print_dest(gboolean to_file, FILE *fh)
226 {
227     /* Close the file or command */
228     if (to_file)
229         return (fclose(fh) == 0);
230     else
231         return (pclose(fh) == 0);
232 }
233 
234 /* Some formats need stuff at the beginning of the output */
235 gboolean
print_preamble(print_stream_t * self,gchar * filename,const char * version_string)236 print_preamble(print_stream_t *self, gchar *filename, const char *version_string)
237 {
238     return self->ops->print_preamble ? (self->ops->print_preamble)(self, filename, version_string) : TRUE;
239 }
240 
241 gboolean
print_line(print_stream_t * self,int indent,const char * line)242 print_line(print_stream_t *self, int indent, const char *line)
243 {
244     return (self->ops->print_line)(self, indent, line);
245 }
246 
247 gboolean
print_line_color(print_stream_t * self,int indent,const char * line,const color_t * fg,const color_t * bg)248 print_line_color(print_stream_t *self, int indent, const char *line, const color_t *fg, const color_t *bg)
249 {
250     if (self->ops->print_line_color)
251         return (self->ops->print_line_color)(self, indent, line, fg, bg);
252     else
253         return (self->ops->print_line)(self, indent, line);
254 }
255 
256 /* Insert bookmark */
257 gboolean
print_bookmark(print_stream_t * self,const gchar * name,const gchar * title)258 print_bookmark(print_stream_t *self, const gchar *name, const gchar *title)
259 {
260     return self->ops->print_bookmark ? (self->ops->print_bookmark)(self, name, title) : TRUE;
261 }
262 
263 gboolean
new_page(print_stream_t * self)264 new_page(print_stream_t *self)
265 {
266     return self->ops->new_page ? (self->ops->new_page)(self) : TRUE;
267 }
268 
269 /* Some formats need stuff at the end of the output */
270 gboolean
print_finale(print_stream_t * self)271 print_finale(print_stream_t *self)
272 {
273     return self->ops->print_finale ? (self->ops->print_finale)(self) : TRUE;
274 }
275 
276 gboolean
destroy_print_stream(print_stream_t * self)277 destroy_print_stream(print_stream_t *self)
278 {
279     return (self && self->ops && self->ops->destroy) ? (self->ops->destroy)(self) : TRUE;
280 }
281 
282 #define MAX_INDENT    160
283 
284 /* returns TRUE if the print succeeded, FALSE if there was an error */
285 static gboolean
print_line_color_text(print_stream_t * self,int indent,const char * line,const color_t * fg,const color_t * bg)286 print_line_color_text(print_stream_t *self, int indent, const char *line, const color_t *fg, const color_t *bg)
287 {
288     static char spaces[MAX_INDENT];
289     size_t ret;
290     output_text *output = (output_text *)self->data;
291     unsigned int num_spaces;
292     gboolean emit_color = output->isatty && (fg != NULL || bg != NULL);
293 
294     /* should be space, if NUL -> initialize */
295     if (!spaces[0])
296         memset(spaces, ' ', sizeof(spaces));
297 
298     if (emit_color) {
299         switch (output->color_type) {
300 
301         case COLOR_NONE:
302             break;
303 
304 #ifdef _WIN32
305         case COLOR_CONSOLE:
306             set_color_console(output->fh, fg, bg);
307             break;
308 #endif
309 
310         case COLOR_24BIT_ESCAPE:
311             set_color_24bit_escape(output->fh, fg, bg);
312             if (ferror(output->fh))
313                 return FALSE;
314             break;
315         }
316     }
317 
318     /* Prepare the tabs for printing, depending on tree level */
319     num_spaces = indent * 4;
320     if (num_spaces > MAX_INDENT)
321         num_spaces = MAX_INDENT;
322 
323     ret = fwrite(spaces, 1, num_spaces, output->fh);
324     if (ret == num_spaces) {
325         if (output->isatty && output->to_codeset) {
326             /* XXX Allocating a fresh buffer every line probably isn't the
327              * most efficient way to do this. However, this has the side
328              * effect of scrubbing invalid output.
329              */
330             gchar *tty_out;
331 
332             tty_out = g_convert_with_fallback(line, -1, output->to_codeset, "UTF-8", "?", NULL, NULL, NULL);
333 
334             if (tty_out) {
335 #ifdef _WIN32
336                 /*
337                  * We mapped to little-endian UTF-16, so write to the
338                  * console using the Unicode API.
339                  */
340                 DWORD out_len = (DWORD) wcslen((wchar_t *) tty_out);
341                 WriteConsoleW((HANDLE)_get_osfhandle(_fileno(output->fh)), tty_out, out_len, &out_len, NULL);
342 #else
343                 fputs(tty_out, output->fh);
344 #endif
345                 g_free(tty_out);
346             } else {
347                 fputs(line, output->fh);
348             }
349         } else {
350             /*
351              * Either we're not writing to a terminal/console or we are
352              * but we're just writing UTF-8 there.
353              */
354             fputs(line, output->fh);
355         }
356 
357 
358         if (emit_color) {
359             switch (output->color_type) {
360 
361             case COLOR_NONE:
362                 putc('\n', output->fh);
363                 break;
364 
365 #ifdef _WIN32
366             case COLOR_CONSOLE:
367                 do_color_eol_console(self);
368                 break;
369 #endif
370 
371             case COLOR_24BIT_ESCAPE:
372                 do_color_eol_24bit_escape(self);
373                 break;
374             }
375         } else
376             putc('\n', output->fh);
377     }
378 
379     return !ferror(output->fh);
380 }
381 
382 static gboolean
print_line_text(print_stream_t * self,int indent,const char * line)383 print_line_text(print_stream_t *self, int indent, const char *line)
384 {
385     return print_line_color_text(self, indent, line, NULL, NULL);
386 }
387 
388 static gboolean
new_page_text(print_stream_t * self)389 new_page_text(print_stream_t *self)
390 {
391     output_text *output = (output_text *)self->data;
392 
393     fputs("\f", output->fh);
394     return !ferror(output->fh);
395 }
396 
397 static gboolean
destroy_text(print_stream_t * self)398 destroy_text(print_stream_t *self)
399 {
400     output_text *output = (output_text *)self->data;
401     gboolean     ret;
402 
403     switch (output->color_type) {
404 
405     case COLOR_NONE:
406         break;
407 
408 #ifdef _WIN32
409     case COLOR_CONSOLE:
410         /* Restore the default text attribute. */
411         SetConsoleTextAttribute((HANDLE)_get_osfhandle(_fileno(output->fh)), output->csb_attrs);
412         break;
413 #endif
414 
415     case COLOR_24BIT_ESCAPE:
416         /* Reset the color to the default */
417         fprintf(output->fh, "%s", TERM_SGR_RESET);
418         fflush(output->fh);
419 
420 #ifdef _WIN32
421         /*
422          * Restore the console mode before we changed it.
423          * We must do that *after* sending escape sequences,
424          * as this may disable escape sequence processing.
425          */
426         SetConsoleMode((HANDLE)_get_osfhandle(_fileno(output->fh)), output->console_mode);
427 #endif
428         break;
429     }
430 
431     ret = close_print_dest(output->to_file, output->fh);
432     g_free(output);
433     g_free(self);
434     return ret;
435 }
436 
437 static const print_stream_ops_t print_text_ops = {
438     NULL,            /* preamble */
439     print_line_text,
440     NULL,            /* bookmark */
441     new_page_text,
442     NULL,            /* finale */
443     destroy_text,
444     print_line_color_text,
445 };
446 
447 static print_stream_t *
print_stream_text_alloc(gboolean to_file,FILE * fh)448 print_stream_text_alloc(gboolean to_file, FILE *fh)
449 {
450     print_stream_t *stream;
451     output_text    *output;
452 
453     output          = (output_text *)g_malloc(sizeof *output);
454     output->to_file = to_file;
455     output->fh      = fh;
456 
457 #ifdef _WIN32
458     /*
459      * On Windows, "_isatty()", which is what ws_isatty() wraps,
460      * "determines whether fd is associated with a character device
461      * (a terminal, console, printer, or serial port)".
462      *
463      * We specifically want to know if it's assciated with a *console*,
464      * as, if it is, we'll be using console-specific APIs.
465      */
466     CONSOLE_SCREEN_BUFFER_INFO csb_info;
467     DWORD console_mode;
468 
469     if (GetConsoleScreenBufferInfo((HANDLE)_get_osfhandle(_fileno(fh)),
470         &csb_info)) {
471         /*
472          * The console-specific API GetConsoleScreenBufferInfo() succeeded,
473          * so we'll assume this is a console.
474          */
475         output->isatty = TRUE;
476         output->csb_attrs = csb_info.wAttributes;
477 
478         /*
479          * Map to little-endian UTF-16; we'll be doing Unicode-API
480          * writes to the console, and that expects the standard flavor
481          * of Unicode on Windows, which is little-endian UTF-16.
482          */
483         output->to_codeset = "UTF-16LE";
484 
485         /*
486          * As indicated above, the classic Windows Console only offers
487          * 1-bit color, set through special console APIs.
488          *
489          * The console in Windows 10 version 1511 (TH2), build 10586, and
490          * later supports SGR escape sequences:
491          *
492          *  http://www.nivot.org/blog/post/2016/02/04/Windows-10-TH2-(v1511)-Console-Host-Enhancements
493          *
494          * but only supports 16 colors.  The "undocumented" 0x04 bit to
495          * which they refer is documented in the current version of the
496          * SetConsoleMode() documentation:
497          *
498          *   https://docs.microsoft.com/en-us/windows/console/setconsolemode
499          *
500          * as ENABLE_VIRTUAL_TERMINAL_PROCESSING, saying
501          *
502          *   When writing with WriteFile or WriteConsole, characters are
503          *   parsed for VT100 and similar control character sequences that
504          *   control cursor movement, color/font mode, and other operations
505          *   that can also be performed via the existing Console APIs. For
506          *   more information, see Console Virtual Terminal Sequences.
507          *
508          * Console Virtual Terminal Sequences:
509          *
510          *   https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
511          *
512          * documents all the escape sequences the Console supports.  It
513          * currently seems to indicate that the ODA versions with 24-bit
514          * color are supported but select the closest color from the
515          * 16-color palette.
516          *
517          * The console in Windows 10 builds 14931 (a preview version of
518          * Windows 10 version 1703) and later supports SGR RGB sequences:
519          *
520          *   https://devblogs.microsoft.com/commandline/24-bit-color-in-the-windows-console/
521          *
522          * That page says:
523          *
524          *   Thanks to our ability to run Linux apps and scripts using our
525          *   new Bash on Ubuntu on Windows environment atop the Windows
526          *   Subsystem for Linux (WSL), we can use some Linux scripts and
527          *   tools to demonstrate the Console's new 24-bit color support:
528          *
529          * which suggests that, with that version, whatever escape sequences
530          * work on UN*Xes also work on Windows, so maybe they support full
531          * 24-bit color with the ODA sequences.
532          *
533          * So, if ENABLE_VIRTUAL_TERMINAL_PROCESSING is already set on
534          * the console, or if it isn't but we can set it, we use the SGR
535          * sequences to set colors, otherwise, we just use the
536          * SetConsoleTextAttribute calls.
537          */
538         GetConsoleMode((HANDLE)_get_osfhandle(_fileno(fh)), &output->console_mode);
539         if (output->console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) {
540             /*
541              * It's already enabled; assume that means we can use the
542              * 24-bit color escape sequences (although the console might
543              * not support full 24-bit color, and would map the 24-bit
544              * color to the closest color in a smaller palette).
545              */
546             output->color_type = COLOR_24BIT_ESCAPE;
547         } else {
548             /*
549              * See if we can enable it.
550              */
551             console_mode = output->console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
552             if (!SetConsoleMode((HANDLE)_get_osfhandle(_fileno(fh)), console_mode)) {
553                 /*
554                  * We can't - use console-mode color.
555                  *
556                  * It's not documented which error is returned if
557                  * you try to set a mode bit that's not supported,
558                  * but, at least on Windows 7, ERROR_INVALID_PARAMETER
559                  * is returned if you try to set
560                  * ENABLE_VIRTUAL_TERMINAL_PROCESSING.
561                  *
562                  * We could check for that error and report other
563                  * errors as failures.
564                  */
565                 output->color_type = COLOR_CONSOLE;
566             } else {
567                 /* We can - use 24-bit color */
568                 output->color_type = COLOR_24BIT_ESCAPE;
569             }
570         }
571     } else {
572         /*
573          * GetConsoleScreenBufferInfo() failed; it's not documented
574          * whether a particular error means "not a console", so we'll
575          * just assume this means it's not a console.
576          *
577          * The error we see on Windows 7 is ERROR_INVALID_HANDLE, but
578          * "invalid" is vague enough that I'm not sure we should
579          * treat that as meaning "not a console and everything else
580          * as being an error that we should report.
581          */
582         output->isatty = FALSE;
583 
584         /*
585          * This is not used if we're not on a console, as we're not doing
586          * coloring.
587          */
588         output->csb_attrs = 0;
589     }
590 #else
591     /*
592      * On UN*X, isatty() tests "whether fildes, an open file descriptor,
593      * is associated with a terminal device", to quote the Single UNIX
594      * Specification, and the documentation for UN*Xes that haven't
595      * been tested against the SUS validation suite say similar things.
596      * It does *not* just test whether it's associated with a character
597      * device that may or may not be a terminal device, so it's what we
598      * want on UN*X.
599      */
600     output->isatty = isatty(ws_fileno(fh));
601     if (output->isatty) {
602         const gchar *charset;
603         gboolean is_utf8;
604 
605         /* Is there a more reliable way to do this? */
606         is_utf8 = g_get_charset(&charset);
607         if (!is_utf8) {
608             /*
609              * The local character set isn't UTF-8, so arrange to
610              * map from UTF-8 to that character set before printing
611              * on the terminal.
612              */
613             output->to_codeset = charset;
614         } else {
615             /*
616              * The local character set is UTF-8, so no mapping is
617              * necessary.
618              */
619             output->to_codeset = NULL;
620         }
621 
622         /*
623          * Not all UN*X terminal emulators support the 24-bit color SGR
624          * sequences (for example, macOS Terminal currently doesn't).
625          *
626          * As per
627          *
628          *    https://github.com/termstandard/colors
629          *
630          * terminfo currently doesn't have a flag to indicate 24-bit
631          * color support - a future release will - so we can't use
632          * that to determine if the terminal emulator (or terminal)
633          * supports it.
634          *
635          * That page notes that some terminal emulators set the
636          * COLORTERM environment variable either to "truecolor"
637          * or "24bit" if 24-bit color is supported; we use that
638          * test for now.
639          *
640          * XXX - if there are terminal emulators that use the 24-bit
641          * color escape sequences but don't set COLORTERM, add code
642          * here to look at other environment variables to try to
643          * recognize them.
644          *
645          * XXX - fall back on 8-color or 256-color support if we can
646          * somehow determine that 24-bit color support isn't available
647          * but 8-color or 256-color support is?
648          */
649         char *colorterm = getenv("COLORTERM");
650         if (colorterm != NULL &&
651             (strcmp(colorterm, "truecolor") == 0 || strcmp(colorterm, "24bit") == 0))
652             output->color_type = COLOR_24BIT_ESCAPE;
653         else
654             output->color_type = COLOR_NONE;
655     }
656 #endif
657     if (!output->isatty) {
658         /*
659          * OK, this was determined *not* to be a terminal, so we won't
660          * be doing coloring or mapping from UTF-8 to a local character
661          * set.
662          */
663         output->to_codeset = NULL;
664         output->color_type = COLOR_NONE;
665     }
666 
667     stream          = g_new(print_stream_t, 1);
668     stream->ops     = &print_text_ops;
669     stream->data    = output;
670 
671     return stream;
672 }
673 
674 print_stream_t *
print_stream_text_new(gboolean to_file,const char * dest)675 print_stream_text_new(gboolean to_file, const char *dest)
676 {
677     FILE *fh;
678 
679     fh = open_print_dest(to_file, dest);
680     if (fh == NULL)
681         return NULL;
682 
683     return print_stream_text_alloc(to_file, fh);
684 }
685 
686 print_stream_t *
print_stream_text_stdio_new(FILE * fh)687 print_stream_text_stdio_new(FILE *fh)
688 {
689     return print_stream_text_alloc(TRUE, fh);
690 }
691 
692 typedef struct {
693     gboolean  to_file;
694     FILE     *fh;
695 } output_ps;
696 
697 #define MAX_PS_LINE_LENGTH 256
698 
699 static
ps_clean_string(char * out,const char * in,int outbuf_size)700 void ps_clean_string(char *out, const char *in, int outbuf_size)
701 {
702     int  rd, wr;
703     char c;
704 
705     if (in == NULL) {
706         out[0] = '\0';
707         return;
708     }
709 
710     for (rd = 0, wr = 0 ; wr < outbuf_size; rd++, wr++ ) {
711         c = in[rd];
712         switch (c) {
713         case '(':
714         case ')':
715         case '\\':
716             out[wr] = '\\';
717             out[++wr] = c;
718             break;
719 
720         default:
721             out[wr] = c;
722             break;
723         }
724 
725         if (c == 0) {
726             break;
727         }
728     }
729 }
730 
731 static gboolean
print_preamble_ps(print_stream_t * self,gchar * filename,const char * version_string)732 print_preamble_ps(print_stream_t *self, gchar *filename, const char *version_string)
733 {
734     output_ps *output = (output_ps *)self->data;
735     char       psbuffer[MAX_PS_LINE_LENGTH]; /* static sized buffer! */
736 
737     print_ps_preamble(output->fh);
738 
739     fputs("%% the page title\n", output->fh);
740     ps_clean_string(psbuffer, filename, MAX_PS_LINE_LENGTH);
741     fprintf(output->fh, "/ws_pagetitle (%s - Wireshark %s) def\n", psbuffer, version_string);
742     fputs("\n", output->fh);
743     return !ferror(output->fh);
744 }
745 
746 static gboolean
print_line_ps(print_stream_t * self,int indent,const char * line)747 print_line_ps(print_stream_t *self, int indent, const char *line)
748 {
749     output_ps *output = (output_ps *)self->data;
750     char       psbuffer[MAX_PS_LINE_LENGTH]; /* static sized buffer! */
751 
752     ps_clean_string(psbuffer, line, MAX_PS_LINE_LENGTH);
753     fprintf(output->fh, "%d (%s) putline\n", indent, psbuffer);
754     return !ferror(output->fh);
755 }
756 
757 static gboolean
print_bookmark_ps(print_stream_t * self,const gchar * name,const gchar * title)758 print_bookmark_ps(print_stream_t *self, const gchar *name, const gchar *title)
759 {
760     output_ps *output = (output_ps *)self->data;
761     char       psbuffer[MAX_PS_LINE_LENGTH]; /* static sized buffer! */
762 
763     /*
764      * See the Adobe "pdfmark reference":
765      *
766      *  http://partners.adobe.com/asn/acrobat/docs/pdfmark.pdf
767      *
768      * The pdfmark stuff tells code that turns PostScript into PDF
769      * things that it should do.
770      *
771      * The /OUT stuff creates a bookmark that goes to the
772      * destination with "name" as the name and "title" as the title.
773      *
774      * The "/DEST" creates the destination.
775      */
776     ps_clean_string(psbuffer, title, MAX_PS_LINE_LENGTH);
777     fprintf(output->fh, "[/Dest /%s /Title (%s)   /OUT pdfmark\n", name,
778           psbuffer);
779     fputs("[/View [/XYZ -4 currentpoint matrix currentmatrix matrix defaultmatrix\n",
780           output->fh);
781     fputs("matrix invertmatrix matrix concatmatrix transform exch pop 20 add null]\n",
782           output->fh);
783     fprintf(output->fh, "/Dest /%s /DEST pdfmark\n", name);
784     return !ferror(output->fh);
785 }
786 
787 static gboolean
new_page_ps(print_stream_t * self)788 new_page_ps(print_stream_t *self)
789 {
790     output_ps *output = (output_ps *)self->data;
791 
792     fputs("formfeed\n", output->fh);
793     return !ferror(output->fh);
794 }
795 
796 static gboolean
print_finale_ps(print_stream_t * self)797 print_finale_ps(print_stream_t *self)
798 {
799     output_ps *output = (output_ps *)self->data;
800 
801     print_ps_finale(output->fh);
802     return !ferror(output->fh);
803 }
804 
805 static gboolean
destroy_ps(print_stream_t * self)806 destroy_ps(print_stream_t *self)
807 {
808     output_ps *output = (output_ps *)self->data;
809     gboolean   ret;
810 
811     ret = close_print_dest(output->to_file, output->fh);
812     g_free(output);
813     g_free(self);
814     return ret;
815 }
816 
817 static const print_stream_ops_t print_ps_ops = {
818     print_preamble_ps,
819     print_line_ps,
820     print_bookmark_ps,
821     new_page_ps,
822     print_finale_ps,
823     destroy_ps,
824     NULL, /* print_line_color */
825 };
826 
827 static print_stream_t *
print_stream_ps_alloc(gboolean to_file,FILE * fh)828 print_stream_ps_alloc(gboolean to_file, FILE *fh)
829 {
830     print_stream_t *stream;
831     output_ps      *output;
832 
833     output          = (output_ps *)g_malloc(sizeof *output);
834     output->to_file = to_file;
835     output->fh      = fh;
836 
837     stream          = g_new(print_stream_t, 1);
838     stream->ops     = &print_ps_ops;
839     stream->data    = output;
840 
841     return stream;
842 }
843 
844 print_stream_t *
print_stream_ps_new(gboolean to_file,const char * dest)845 print_stream_ps_new(gboolean to_file, const char *dest)
846 {
847     FILE *fh;
848 
849     fh = open_print_dest(to_file, dest);
850     if (fh == NULL)
851         return NULL;
852 
853     return print_stream_ps_alloc(to_file, fh);
854 }
855 
856 print_stream_t *
print_stream_ps_stdio_new(FILE * fh)857 print_stream_ps_stdio_new(FILE *fh)
858 {
859     return print_stream_ps_alloc(TRUE, fh);
860 }
861 
862 /*
863  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
864  *
865  * Local variables:
866  * c-basic-offset: 4
867  * tab-width: 8
868  * indent-tabs-mode: nil
869  * End:
870  *
871  * vi: set shiftwidth=4 tabstop=8 expandtab:
872  * :indentSize=4:tabSize=8:noTabs=true:
873  */
874