/src/wireshark/epan/print_stream.c
Line | Count | Source |
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 | | bool to_file; |
50 | | FILE *fh; |
51 | | bool 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 |
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 |
137 | | set_color_24bit_escape(FILE *fh, const color_t *fg, const color_t *bg) |
138 | 0 | { |
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: Character 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 | 0 | if (fg) { |
169 | 0 | fprintf(fh, "\x1B[38;2;%u;%u;%um", |
170 | 0 | (fg->red >> 8) & 0xff, |
171 | 0 | (fg->green >> 8) & 0xff, |
172 | 0 | (fg->blue >> 8) & 0xff); |
173 | 0 | } |
174 | |
|
175 | 0 | if (bg) { |
176 | 0 | fprintf(fh, "\x1B[48;2;%u;%u;%um", |
177 | 0 | (bg->red >> 8) & 0xff, |
178 | 0 | (bg->green >> 8) & 0xff, |
179 | 0 | (bg->blue >> 8) & 0xff); |
180 | 0 | } |
181 | 0 | } |
182 | | |
183 | | #ifdef _WIN32 |
184 | | static void |
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 |
196 | | do_color_eol_24bit_escape(print_stream_t *self) |
197 | 0 | { |
198 | 0 | output_text *output = (output_text *)self->data; |
199 | 0 | 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 | 0 | fprintf(fh, "%s\n%s", TERM_CSI_EL, TERM_SGR_RESET); |
208 | 0 | } |
209 | | |
210 | | static FILE * |
211 | | open_print_dest(bool to_file, const char *dest) |
212 | 0 | { |
213 | 0 | FILE *fh; |
214 | | |
215 | | /* Open the file or command for output */ |
216 | 0 | if (to_file) |
217 | 0 | fh = ws_fopen(dest, "w"); |
218 | 0 | else |
219 | 0 | fh = popen(dest, "w"); |
220 | |
|
221 | 0 | return fh; |
222 | 0 | } |
223 | | |
224 | | static bool |
225 | | close_print_dest(bool to_file, FILE *fh) |
226 | 0 | { |
227 | | /* Close the file or command */ |
228 | 0 | if (to_file) |
229 | 0 | return (fclose(fh) == 0); |
230 | 0 | else |
231 | 0 | return (pclose(fh) == 0); |
232 | 0 | } |
233 | | |
234 | | /* Some formats need stuff at the beginning of the output */ |
235 | | bool |
236 | | print_preamble(print_stream_t *self, char *filename, const char *version_string) |
237 | 0 | { |
238 | 0 | return self->ops->print_preamble ? (self->ops->print_preamble)(self, filename, version_string) : true; |
239 | 0 | } |
240 | | |
241 | | bool |
242 | | print_line(print_stream_t *self, int indent, const char *line) |
243 | 0 | { |
244 | 0 | return (self->ops->print_line)(self, indent, line); |
245 | 0 | } |
246 | | |
247 | | bool |
248 | | print_line_color(print_stream_t *self, int indent, const char *line, const color_t *fg, const color_t *bg) |
249 | 0 | { |
250 | 0 | if (self->ops->print_line_color) |
251 | 0 | return (self->ops->print_line_color)(self, indent, line, fg, bg); |
252 | 0 | else |
253 | 0 | return (self->ops->print_line)(self, indent, line); |
254 | 0 | } |
255 | | |
256 | | /* Insert bookmark */ |
257 | | bool |
258 | | print_bookmark(print_stream_t *self, const char *name, const char *title) |
259 | 0 | { |
260 | 0 | return self->ops->print_bookmark ? (self->ops->print_bookmark)(self, name, title) : true; |
261 | 0 | } |
262 | | |
263 | | bool |
264 | | new_page(print_stream_t *self) |
265 | 0 | { |
266 | 0 | return self->ops->new_page ? (self->ops->new_page)(self) : true; |
267 | 0 | } |
268 | | |
269 | | /* Some formats need stuff at the end of the output */ |
270 | | bool |
271 | | print_finale(print_stream_t *self) |
272 | 0 | { |
273 | 0 | return self->ops->print_finale ? (self->ops->print_finale)(self) : true; |
274 | 0 | } |
275 | | |
276 | | bool |
277 | | destroy_print_stream(print_stream_t *self) |
278 | 0 | { |
279 | 0 | return (self && self->ops && self->ops->destroy) ? (self->ops->destroy)(self) : true; |
280 | 0 | } |
281 | | |
282 | 0 | #define MAX_INDENT 1024 |
283 | | |
284 | | /* returns true if the print succeeded, false if there was an error */ |
285 | | static bool |
286 | | print_line_color_text(print_stream_t *self, int indent, const char *line, const color_t *fg, const color_t *bg) |
287 | 0 | { |
288 | 0 | static char spaces[MAX_INDENT]; |
289 | 0 | size_t ret; |
290 | 0 | output_text *output = (output_text *)self->data; |
291 | 0 | unsigned int num_spaces; |
292 | 0 | bool emit_color = output->isatty && (fg != NULL || bg != NULL); |
293 | | |
294 | | /* should be space, if NUL -> initialize */ |
295 | 0 | if (!spaces[0]) |
296 | 0 | memset(spaces, ' ', sizeof(spaces)); |
297 | |
|
298 | 0 | if (emit_color) { |
299 | 0 | switch (output->color_type) { |
300 | | |
301 | 0 | case COLOR_NONE: |
302 | 0 | break; |
303 | | |
304 | | #ifdef _WIN32 |
305 | | case COLOR_CONSOLE: |
306 | | set_color_console(output->fh, fg, bg); |
307 | | break; |
308 | | #endif |
309 | | |
310 | 0 | case COLOR_24BIT_ESCAPE: |
311 | 0 | set_color_24bit_escape(output->fh, fg, bg); |
312 | 0 | if (ferror(output->fh)) |
313 | 0 | return false; |
314 | 0 | break; |
315 | 0 | } |
316 | 0 | } |
317 | | |
318 | | /* Prepare the tabs for printing, depending on tree level */ |
319 | 0 | num_spaces = indent * 4; |
320 | 0 | if (num_spaces > MAX_INDENT) |
321 | 0 | num_spaces = MAX_INDENT; |
322 | |
|
323 | 0 | ret = fwrite(spaces, 1, num_spaces, output->fh); |
324 | 0 | if (ret == num_spaces) { |
325 | 0 | 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 | 0 | char *tty_out; |
331 | |
|
332 | 0 | tty_out = g_convert_with_fallback(line, -1, output->to_codeset, "UTF-8", "?", NULL, NULL, NULL); |
333 | |
|
334 | 0 | 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 | 0 | fputs(tty_out, output->fh); |
344 | 0 | #endif |
345 | 0 | g_free(tty_out); |
346 | 0 | } else { |
347 | 0 | fputs(line, output->fh); |
348 | 0 | } |
349 | 0 | } 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 | 0 | fputs(line, output->fh); |
355 | 0 | } |
356 | | |
357 | |
|
358 | 0 | if (emit_color) { |
359 | 0 | switch (output->color_type) { |
360 | | |
361 | 0 | case COLOR_NONE: |
362 | 0 | putc('\n', output->fh); |
363 | 0 | break; |
364 | | |
365 | | #ifdef _WIN32 |
366 | | case COLOR_CONSOLE: |
367 | | do_color_eol_console(self); |
368 | | break; |
369 | | #endif |
370 | | |
371 | 0 | case COLOR_24BIT_ESCAPE: |
372 | 0 | do_color_eol_24bit_escape(self); |
373 | 0 | break; |
374 | 0 | } |
375 | 0 | } else |
376 | 0 | putc('\n', output->fh); |
377 | 0 | } |
378 | | |
379 | 0 | return !ferror(output->fh); |
380 | 0 | } |
381 | | |
382 | | static bool |
383 | | print_line_text(print_stream_t *self, int indent, const char *line) |
384 | 0 | { |
385 | 0 | return print_line_color_text(self, indent, line, NULL, NULL); |
386 | 0 | } |
387 | | |
388 | | static bool |
389 | | new_page_text(print_stream_t *self) |
390 | 0 | { |
391 | 0 | output_text *output = (output_text *)self->data; |
392 | |
|
393 | 0 | fputs("\f", output->fh); |
394 | 0 | return !ferror(output->fh); |
395 | 0 | } |
396 | | |
397 | | static bool |
398 | | destroy_text(print_stream_t *self) |
399 | 0 | { |
400 | 0 | output_text *output = (output_text *)self->data; |
401 | 0 | bool ret; |
402 | |
|
403 | 0 | switch (output->color_type) { |
404 | | |
405 | 0 | case COLOR_NONE: |
406 | 0 | 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 | 0 | case COLOR_24BIT_ESCAPE: |
416 | | /* Reset the color to the default */ |
417 | 0 | fprintf(output->fh, "%s", TERM_SGR_RESET); |
418 | 0 | 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 | 0 | break; |
429 | 0 | } |
430 | | |
431 | 0 | ret = close_print_dest(output->to_file, output->fh); |
432 | 0 | g_free(output); |
433 | 0 | g_free(self); |
434 | 0 | return ret; |
435 | 0 | } |
436 | | |
437 | | static const print_stream_ops_t print_text_ops = { |
438 | | NULL, /* preamble */ |
439 | | print_line_text, |
440 | | print_line_color_text, |
441 | | NULL, /* bookmark */ |
442 | | new_page_text, |
443 | | NULL, /* finale */ |
444 | | destroy_text, |
445 | | }; |
446 | | |
447 | | static print_stream_t * |
448 | | print_stream_text_alloc(bool to_file, FILE *fh) |
449 | 0 | { |
450 | 0 | print_stream_t *stream; |
451 | 0 | output_text *output; |
452 | |
|
453 | 0 | output = (output_text *)g_malloc(sizeof *output); |
454 | 0 | output->to_file = to_file; |
455 | 0 | 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 associated 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 | 0 | output->isatty = isatty(ws_fileno(fh)); |
601 | 0 | if (output->isatty) { |
602 | 0 | const char *charset; |
603 | 0 | bool is_utf8; |
604 | | |
605 | | /* Is there a more reliable way to do this? */ |
606 | 0 | is_utf8 = g_get_charset(&charset); |
607 | 0 | 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 | 0 | output->to_codeset = charset; |
614 | 0 | } else { |
615 | | /* |
616 | | * The local character set is UTF-8, so no mapping is |
617 | | * necessary. |
618 | | */ |
619 | 0 | output->to_codeset = NULL; |
620 | 0 | } |
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 | 0 | char *colorterm = getenv("COLORTERM"); |
650 | 0 | if (colorterm != NULL && |
651 | 0 | (strcmp(colorterm, "truecolor") == 0 || strcmp(colorterm, "24bit") == 0)) |
652 | 0 | output->color_type = COLOR_24BIT_ESCAPE; |
653 | 0 | else |
654 | 0 | output->color_type = COLOR_NONE; |
655 | 0 | } |
656 | 0 | #endif |
657 | 0 | 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 | 0 | output->to_codeset = NULL; |
664 | 0 | output->color_type = COLOR_NONE; |
665 | 0 | } |
666 | |
|
667 | 0 | stream = g_new(print_stream_t, 1); |
668 | 0 | stream->ops = &print_text_ops; |
669 | 0 | stream->data = output; |
670 | |
|
671 | 0 | return stream; |
672 | 0 | } |
673 | | |
674 | | print_stream_t * |
675 | | print_stream_text_new(bool to_file, const char *dest) |
676 | 0 | { |
677 | 0 | FILE *fh; |
678 | |
|
679 | 0 | fh = open_print_dest(to_file, dest); |
680 | 0 | if (fh == NULL) |
681 | 0 | return NULL; |
682 | | |
683 | 0 | return print_stream_text_alloc(to_file, fh); |
684 | 0 | } |
685 | | |
686 | | print_stream_t * |
687 | | print_stream_text_stdio_new(FILE *fh) |
688 | 0 | { |
689 | 0 | return print_stream_text_alloc(true, fh); |
690 | 0 | } |
691 | | |
692 | | typedef struct { |
693 | | bool to_file; |
694 | | FILE *fh; |
695 | | } output_ps; |
696 | | |
697 | 0 | #define MAX_PS_LINE_LENGTH 256 |
698 | | |
699 | | static |
700 | | void ps_clean_string(char *out, const char *in, int outbuf_size) |
701 | 0 | { |
702 | 0 | int rd, wr; |
703 | 0 | char c; |
704 | |
|
705 | 0 | if (in == NULL) { |
706 | 0 | out[0] = '\0'; |
707 | 0 | return; |
708 | 0 | } |
709 | | |
710 | 0 | for (rd = 0, wr = 0 ; wr < outbuf_size; rd++, wr++ ) { |
711 | 0 | c = in[rd]; |
712 | 0 | switch (c) { |
713 | 0 | case '(': |
714 | 0 | case ')': |
715 | 0 | case '\\': |
716 | 0 | out[wr] = '\\'; |
717 | 0 | out[++wr] = c; |
718 | 0 | break; |
719 | | |
720 | 0 | default: |
721 | 0 | out[wr] = c; |
722 | 0 | break; |
723 | 0 | } |
724 | | |
725 | 0 | if (c == 0) { |
726 | 0 | break; |
727 | 0 | } |
728 | 0 | } |
729 | 0 | } |
730 | | |
731 | | static bool |
732 | | print_preamble_ps(print_stream_t *self, char *filename, const char *version_string) |
733 | 0 | { |
734 | 0 | output_ps *output = (output_ps *)self->data; |
735 | 0 | char psbuffer[MAX_PS_LINE_LENGTH]; /* static sized buffer! */ |
736 | |
|
737 | 0 | print_ps_preamble(output->fh); |
738 | |
|
739 | 0 | fputs("%% the page title\n", output->fh); |
740 | 0 | ps_clean_string(psbuffer, filename, MAX_PS_LINE_LENGTH); |
741 | 0 | fprintf(output->fh, "/ws_pagetitle (%s - Wireshark %s) def\n", psbuffer, version_string); |
742 | 0 | fputs("\n", output->fh); |
743 | 0 | return !ferror(output->fh); |
744 | 0 | } |
745 | | |
746 | | static bool |
747 | | print_line_ps(print_stream_t *self, int indent, const char *line) |
748 | 0 | { |
749 | 0 | output_ps *output = (output_ps *)self->data; |
750 | 0 | char psbuffer[MAX_PS_LINE_LENGTH]; /* static sized buffer! */ |
751 | |
|
752 | 0 | ps_clean_string(psbuffer, line, MAX_PS_LINE_LENGTH); |
753 | 0 | fprintf(output->fh, "%d (%s) putline\n", indent, psbuffer); |
754 | 0 | return !ferror(output->fh); |
755 | 0 | } |
756 | | |
757 | | static bool |
758 | | print_bookmark_ps(print_stream_t *self, const char *name, const char *title) |
759 | 0 | { |
760 | 0 | output_ps *output = (output_ps *)self->data; |
761 | 0 | 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 | 0 | ps_clean_string(psbuffer, title, MAX_PS_LINE_LENGTH); |
777 | 0 | fprintf(output->fh, "[/Dest /%s /Title (%s) /OUT pdfmark\n", name, |
778 | 0 | psbuffer); |
779 | 0 | fputs("[/View [/XYZ -4 currentpoint matrix currentmatrix matrix defaultmatrix\n", |
780 | 0 | output->fh); |
781 | 0 | fputs("matrix invertmatrix matrix concatmatrix transform exch pop 20 add null]\n", |
782 | 0 | output->fh); |
783 | 0 | fprintf(output->fh, "/Dest /%s /DEST pdfmark\n", name); |
784 | 0 | return !ferror(output->fh); |
785 | 0 | } |
786 | | |
787 | | static bool |
788 | | new_page_ps(print_stream_t *self) |
789 | 0 | { |
790 | 0 | output_ps *output = (output_ps *)self->data; |
791 | |
|
792 | 0 | fputs("formfeed\n", output->fh); |
793 | 0 | return !ferror(output->fh); |
794 | 0 | } |
795 | | |
796 | | static bool |
797 | | print_finale_ps(print_stream_t *self) |
798 | 0 | { |
799 | 0 | output_ps *output = (output_ps *)self->data; |
800 | |
|
801 | 0 | print_ps_finale(output->fh); |
802 | 0 | return !ferror(output->fh); |
803 | 0 | } |
804 | | |
805 | | static bool |
806 | | destroy_ps(print_stream_t *self) |
807 | 0 | { |
808 | 0 | output_ps *output = (output_ps *)self->data; |
809 | 0 | bool ret; |
810 | |
|
811 | 0 | ret = close_print_dest(output->to_file, output->fh); |
812 | 0 | g_free(output); |
813 | 0 | g_free(self); |
814 | 0 | return ret; |
815 | 0 | } |
816 | | |
817 | | static const print_stream_ops_t print_ps_ops = { |
818 | | print_preamble_ps, |
819 | | print_line_ps, |
820 | | NULL, /* print_line_color */ |
821 | | print_bookmark_ps, |
822 | | new_page_ps, |
823 | | print_finale_ps, |
824 | | destroy_ps, |
825 | | }; |
826 | | |
827 | | static print_stream_t * |
828 | | print_stream_ps_alloc(bool to_file, FILE *fh) |
829 | 0 | { |
830 | 0 | print_stream_t *stream; |
831 | 0 | output_ps *output; |
832 | |
|
833 | 0 | output = (output_ps *)g_malloc(sizeof *output); |
834 | 0 | output->to_file = to_file; |
835 | 0 | output->fh = fh; |
836 | |
|
837 | 0 | stream = g_new(print_stream_t, 1); |
838 | 0 | stream->ops = &print_ps_ops; |
839 | 0 | stream->data = output; |
840 | |
|
841 | 0 | return stream; |
842 | 0 | } |
843 | | |
844 | | print_stream_t * |
845 | | print_stream_ps_new(bool to_file, const char *dest) |
846 | 0 | { |
847 | 0 | FILE *fh; |
848 | |
|
849 | 0 | fh = open_print_dest(to_file, dest); |
850 | 0 | if (fh == NULL) |
851 | 0 | return NULL; |
852 | | |
853 | 0 | return print_stream_ps_alloc(to_file, fh); |
854 | 0 | } |
855 | | |
856 | | print_stream_t * |
857 | | print_stream_ps_stdio_new(FILE *fh) |
858 | 0 | { |
859 | | return print_stream_ps_alloc(true, fh); |
860 | 0 | } |
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 | | */ |