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