1 /*
2  * Copyright (c) 1994-2020 Paul Mattes.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the names of Paul Mattes nor the names of his contributors
13  *       may be used to endorse or promote products derived from this software
14  *       without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY PAUL MATTES "AS IS" AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL PAUL MATTES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 /*
29  *	print_screen.c
30  *		Screen printing functions.
31  */
32 
33 #include "globals.h"
34 
35 #include "appres.h"
36 #include "3270ds.h"
37 #include "ctlr.h"
38 
39 #include "ctlrc.h"
40 
41 #include <errno.h>
42 #include <assert.h>
43 
44 #include "resources.h"
45 
46 #include "actions.h"
47 #include "codepage.h"
48 #include "fprint_screen.h"
49 #include "lazya.h"
50 #include "names.h"
51 #include "popups.h"
52 #include "print_command.h"
53 #include "print_screen.h"
54 #include "print_gui.h"
55 #include "product.h"
56 #include "task.h"
57 #include "toggles.h"
58 #include "trace.h"
59 #include "unicodec.h"
60 #include "utils.h"
61 
62 #if defined(_WIN32) /*[*/
63 # include <fcntl.h>
64 # include <sys/stat.h>
65 # include "w3misc.h"
66 # include "winprint.h"
67 #endif /*]*/
68 
69 /* Typedefs */
70 
71 /* Saved context for a suspended PrintText(). */
72 typedef struct {
73     FILE *f;		/* temporary file */
74     ptype_t ptype;	/* print type */
75     unsigned opts;	/* options */
76     const char *caption; /* caption text */
77     const char *name;	/* printer name */
78     char *temp_name;	/* temporary file name */
79 } printtext_t;
80 
81 /* Globals */
82 
83 /* Statics */
84 
85 /**
86  * Default caption.
87  *
88  * @return caption text
89  */
90 char *
default_caption(void)91 default_caption(void)
92 {
93     static char *r = NULL;
94 
95 #if !defined(_WIN32) /*[*/
96     /* Unix version: username@host %T% */
97     char hostname[132];
98     char *user;
99 
100     gethostname(hostname, sizeof(hostname));
101     hostname[sizeof(hostname) - 1] = '\0';
102     user = getenv("USER");
103     if (user == NULL) {
104 	user = "(unknown)";
105     }
106 
107     if (r != NULL) {
108 	Free(r);
109     }
110     r = xs_buffer("%s @ %s %%T%%", user, hostname);
111 
112 #else /*][*/
113 
114     char *username;
115     char *computername;
116     char *userdomain;
117     char ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
118 
119     username = getenv("USERNAME");
120     if (username == NULL) {
121 	username = "(unknown)";
122     }
123     computername = getenv("COMPUTERNAME");
124     if (computername == NULL) {
125 	DWORD size = MAX_COMPUTERNAME_LENGTH + 1;
126 
127 	if (GetComputerName(ComputerName, &size)) {
128 	    computername = ComputerName;
129 	} else {
130 	    computername = "(unknown)";
131 	}
132     }
133     userdomain = getenv("USERDOMAIN");
134     if (userdomain == NULL) {
135 	userdomain = "(unknown)";
136     }
137 
138     if (r != NULL) {
139 	Free(r);
140     }
141     if (strcasecmp(userdomain, computername)) {
142 	r = xs_buffer("%s\\%s @ %s %%T%%", userdomain, username, computername);
143     } else {
144 	r = xs_buffer("%s @ %s %%T%%", username, computername);
145     }
146 #endif
147 
148     return r;
149 }
150 
151 /* Extended-wait continue function for PrintText(). */
152 static void
printtext_continue(void * context,bool cancel)153 printtext_continue(void *context, bool cancel)
154 {
155     printtext_t *pt = (printtext_t *)context;
156     fps_status_t status;
157 
158     if (cancel) {
159 	vtrace("PrintText canceled\n");
160 	fclose(pt->f);
161 	if (pt->temp_name != NULL) {
162 	    unlink(pt->temp_name);
163 	    Free(pt->temp_name);
164 	}
165 	Free(pt);
166 	return;
167     }
168 
169     status = fprint_screen(pt->f, pt->ptype, pt->opts | FPS_DIALOG_COMPLETE,
170 	    pt->caption, pt->name, NULL);
171     switch (status) {
172     case FPS_STATUS_SUCCESS:
173     case FPS_STATUS_SUCCESS_WRITTEN:
174 	vtrace("PrintText: printing succeeded.\n");
175 	break;
176     case FPS_STATUS_ERROR:
177 	popup_an_error("Screen print failed");
178 	/* fall through */
179     case FPS_STATUS_CANCEL:
180 	if (status == FPS_STATUS_CANCEL) {
181 	    vtrace("PrintText: printing canceled.\n");
182 	}
183 	break;
184     case FPS_STATUS_WAIT:
185 	/* Can't happen. */
186 	assert(status != FPS_STATUS_WAIT);
187 	break;
188     }
189 
190     fclose(pt->f);
191     if (pt->temp_name != NULL) {
192 	unlink(pt->temp_name);
193 	Free(pt->temp_name);
194     }
195     Free(pt);
196 }
197 
198 /* Print or save the contents of the screen as text. */
199 bool
PrintText_action(ia_t ia,unsigned argc,const char ** argv)200 PrintText_action(ia_t ia, unsigned argc, const char **argv)
201 {
202     enum { PM_NONE, PM_FILE, PM_GDI, PM_COMMAND, PM_STRING } mode = PM_NONE;
203     unsigned i;
204     const char *name = NULL;
205     bool secure = appres.secure;
206     ptype_t ptype = P_NONE;
207     bool replace = false;
208     char *temp_name = NULL;
209     unsigned opts = FPS_EVEN_IF_EMPTY;
210     const char *caption = NULL;
211     FILE *f;
212     int fd = -1;
213     fps_status_t status;
214     printtext_t *pt;
215     bool any_file_options = false;
216 #if defined(_WIN32) /*[*/
217     bool any_gdi_options = false;
218 #endif /*]*/
219     bool use_file = false;
220 
221     if (!appres.interactive.print_dialog) {
222 	opts |= FPS_NO_DIALOG;
223     }
224 
225     action_debug(AnPrintText, ia, argc, argv);
226 
227     /*
228      * Pick off optional arguments:
229      *  file     directs the output to a file instead of a command;
230      *  	      must be the last keyword
231      *  html     generates HTML output instead of ASCII text (and implies
232      *            'file')
233      *  rtf      generates RTF output instead of ASCII text (and implies
234      *            'file')
235      *  gdi      prints to a GDI printer (Windows only)
236      *  nodialog skip print dialog (Windows only)
237      *            this is the default for ws3270
238      *  dialog	 use print dialog (Windows only)
239      *            this is the default for wc3270
240      *  replace  replace the file
241      *  append   append to the file, if it exists (default)
242      *  modi     print modified fields in italics
243      *  caption "text"
244      *           Adds caption text above the screen
245      *           %T% is replaced by a timestamp
246      *  secure   disables the pop-up dialog, if this action is invoked from
247      *            a keymap (x3270 only)
248      *  command  directs the output to a command (this is the default, but
249      *            allows the command to be one of the other keywords);
250      *  	      must be the last keyword
251      *  string   returns the data as a string */
252     for (i = 0; i < argc; i++) {
253 	if (!strcasecmp(argv[i], KwFile)) {
254 	    if (mode != PM_NONE) {
255 		popup_an_error(AnPrintText "(): contradictory option '%s'",
256 			argv[i]);
257 		return false;
258 	    }
259 	    mode = PM_FILE;
260 	    i++;
261 	    break;
262 	} else if (!strcasecmp(argv[i], KwHtml)) {
263 	    if (ptype != P_NONE) {
264 		popup_an_error(AnPrintText "(): contradictory option '%s'",
265 			argv[i]);
266 		return false;
267 	    }
268 	    ptype = P_HTML;
269 	} else if (!strcasecmp(argv[i], KwRtf)) {
270 	    if (ptype != P_NONE) {
271 		popup_an_error(AnPrintText "(): contradictory option '%s'",
272 			argv[i]);
273 		return false;
274 	    }
275 	    ptype = P_RTF;
276 	} else if (!strcasecmp(argv[i], KwReplace)) {
277 	    replace = true;
278 	    any_file_options = true;
279 	} else if (!strcasecmp(argv[i], KwAppend)) {
280 	    replace = false;
281 	    any_file_options = true;
282 	}
283 #if defined(_WIN32) /*[*/
284 	else if (!strcasecmp(argv[i], KwGdi)) {
285 	    if (mode != PM_NONE) {
286 		popup_an_error(AnPrintText "(): contradictory option '%s'",
287 			argv[i]);
288 		return false;
289 	    }
290 	    mode = PM_GDI;
291 	} else if (!strcasecmp(argv[i], KwNoDialog)) {
292 	    opts |= FPS_NO_DIALOG;
293 	    any_gdi_options = true;
294 	} else if (!strcasecmp(argv[i], KwDialog)) {
295 	    opts &= ~FPS_NO_DIALOG;
296 	    any_gdi_options = true;
297 	}
298 #endif /*]*/
299 	else if (!strcasecmp(argv[i], KwSecure)) {
300 	    secure = true;
301 	}
302 #if !defined(_WIN32) /*[*/
303 	else if (!strcasecmp(argv[i], KwCommand)) {
304 	    if (mode != PM_NONE) {
305 		popup_an_error(AnPrintText "(): contradictory option '%s'",
306 			argv[i]);
307 		return false;
308 	    }
309 	    mode = PM_COMMAND;
310 	    i++;
311 	    break;
312 	}
313 #endif /*]*/
314 	else if (!strcasecmp(argv[i], KwString)) {
315 	    if (mode != PM_NONE) {
316 		popup_an_error(AnPrintText "(): contradictory option '%s'",
317 			argv[i]);
318 		return false;
319 	    }
320 	    mode = PM_STRING;
321 	} else if (!strcasecmp(argv[i], KwModi)) {
322 	    opts |= FPS_MODIFIED_ITALIC;
323 	} else if (!strcasecmp(argv[i], KwCaption)) {
324 	    if (i == argc - 1) {
325 		popup_an_error(AnPrintText "(): missing " KwCaption
326 			" parameter");
327 		return false;
328 	    }
329 	    caption = argv[++i];
330 	} else {
331 	    break;
332 	}
333     }
334 
335     /* Set the default mode, if none has been selected. */
336     if (mode == PM_NONE) {
337 #if !defined(_WIN32) /*[*/
338 	mode = PM_COMMAND;
339 #else /*][*/
340 	mode = PM_GDI;
341 #endif /*]*/
342     }
343 
344     /* Root out some additional option conflicts. */
345     if (any_file_options && mode != PM_FILE) {
346 	popup_an_error(AnPrintText "(): " KwFile "-related option(s) given "
347 		"when not printing to file");
348 	return false;
349     }
350 #if defined(_WIN32) /*[*/
351     if (any_gdi_options && mode != PM_GDI) {
352 	popup_an_error(AnPrintText "(): " KwGdi "-related option(s) given "
353 		"when not printing via GDI");
354 	return false;
355     }
356 #endif /*]*/
357 
358     /* Handle positional options. */
359     switch (argc - i) {
360     case 0:
361 	/* Use the default command or printer. */
362 #if !defined(_WIN32) /*[*/
363 	if (mode == PM_COMMAND) {
364 	    name = get_resource(ResPrintTextCommand);
365 	    if (name == NULL || !*name) {
366 		name = "lpr";
367 	    }
368 	}
369 #else /*][*/
370 	if (mode == PM_GDI) {
371 	    name = get_resource(ResPrinterName);
372 	}
373 #endif /*]*/
374 	break;
375     case 1:
376 	if (mode == PM_STRING) {
377 	    popup_an_error(AnPrintText "(): extra argument "
378 		    "with '" KwString "'");
379 	    return false;
380 	}
381 	name = argv[i];
382 	break;
383     default:
384 	popup_an_error(AnPrintText "(): extra arguments");
385 	return false;
386     }
387 
388     /* Infer the type from the file suffix. */
389     if (mode == PM_FILE && ptype == P_NONE && name != NULL) {
390 	size_t sl = strlen(name);
391 
392 	if ((sl > 5 && !strcasecmp(name + sl - 5, ".html")) ||
393 	    (sl > 4 && !strcasecmp(name + sl - 4, ".htm"))) {
394 	    ptype = P_HTML;
395 	} else if (sl > 4 && !strcasecmp(name + sl - 4, ".rtf")) {
396 	    ptype = P_RTF;
397 	}
398     }
399 
400     /* Figure out the default ptype, if still not selected. */
401     if (ptype == P_NONE) {
402 	if (mode == PM_GDI) {
403 	    ptype = P_GDI;
404 	} else {
405 	    ptype = P_TEXT;
406 	}
407     }
408 
409     if (name != NULL && name[0] == '@') {
410 	/*
411 	 * Starting the PrintTextCommand resource value with '@'
412 	 * suppresses the pop-up dialog, as does setting the 'secure'
413 	 * resource.
414 	 */
415 	secure = true;
416 	name++;
417     }
418 
419     /* See if the GUI wants to handle it. */
420     if (!secure && print_text_gui(mode == PM_FILE)) {
421 	return true;
422     }
423 
424     /* Do the real work. */
425     use_file = (mode == PM_FILE || mode == PM_STRING);
426     if (use_file) {
427 	if (mode == PM_STRING) {
428 #if defined(_WIN32) /*[*/
429 	    fd = win_mkstemp(&temp_name, ptype);
430 #else /*][*/
431 	    temp_name = NewString("/tmp/x3hXXXXXX");
432 	    fd = mkstemp(temp_name);
433 #endif /*]*/
434 	    if (fd < 0) {
435 		popup_an_errno(errno, "mkstemp");
436 		return false;
437 	    }
438 	    f = fdopen(fd, "w+");
439 	    vtrace("PrintText: using '%s'\n", temp_name);
440 	} else {
441 	    if (name == NULL || !*name) {
442 		popup_an_error(AnPrintText "(): missing filename");
443 		return false;
444 	    }
445 	    f = fopen(name, replace? "w": "a");
446 	}
447     } else {
448 #if !defined(_WIN32) /*[*/
449 	const char *expanded_name;
450 	char *pct_e;
451 
452 	if ((pct_e = strstr(name, "%E%")) != NULL) {
453 	    expanded_name = lazyaf("%.*s%s%s",
454 		    (int)(pct_e - name), name,
455 		    programname,
456 		    pct_e + 3);
457 	} else {
458 	    expanded_name = name;
459 	}
460 	f = printer_open(expanded_name, NULL);
461 #else /*][*/
462 	fd = win_mkstemp(&temp_name, ptype);
463 	if (fd < 0) {
464 	    popup_an_errno(errno, "mkstemp");
465 	    return false;
466 	}
467 	if (ptype == P_GDI) {
468 	    f = fdopen(fd, "wb+");
469 	} else {
470 	    f = fdopen(fd, "w+");
471 	}
472 	vtrace("PrintText: using '%s'\n", temp_name);
473 #endif /*]*/
474     }
475     if (f == NULL) {
476 	popup_an_errno(errno, AnPrintText "(): %s", name);
477 	if (fd >= 0) {
478 	    close(fd);
479 	}
480 	if (temp_name) {
481 	    unlink(temp_name);
482 	    Free(temp_name);
483 	}
484 	return false;
485     }
486 
487     /* Captions look nice on GDI, so create a default one. */
488     if (ptype == P_GDI && caption == NULL) {
489 	caption = default_caption();
490     }
491 
492     pt = (printtext_t *)Calloc(1, sizeof(printtext_t));
493     status = fprint_screen(f, ptype, opts, caption, name, pt);
494     switch (status) {
495     case FPS_STATUS_SUCCESS:
496     case FPS_STATUS_SUCCESS_WRITTEN:
497 	vtrace("PrintText: printing succeeded.\n");
498 	Free(pt);
499 	break;
500     case FPS_STATUS_ERROR:
501 	popup_an_error("Screen print failed.");
502 	/* fall through */
503     case FPS_STATUS_CANCEL:
504 	if (status == FPS_STATUS_CANCEL) {
505 	    vtrace("PrintText: printing canceled.\n");
506 	}
507 	Free(pt);
508 	fclose(f);
509 	if (temp_name) {
510 	    unlink(temp_name);
511 	    Free(temp_name);
512 	}
513 	return false;
514     case FPS_STATUS_WAIT:
515 	/* Waiting for asynchronous activity. */
516 	assert(ptype == P_GDI);
517 	pt->f = f;
518 	pt->ptype = ptype;
519 	pt->opts = opts;
520 	pt->caption = caption;
521 	pt->name = name;
522 	pt->temp_name = temp_name;
523 	task_xwait(pt, printtext_continue, "printing");
524 	return true;
525     }
526 
527     if (mode == PM_STRING) {
528 	char buf[8192];
529 
530 	/* Print to string. */
531 	fflush(f);
532 	rewind(f);
533 	while (fgets(buf, sizeof(buf), f) != NULL) {
534 	    action_output("%s", buf);
535 	}
536 	fclose(f);
537 	unlink(temp_name);
538 	Free(temp_name);
539 	return true;
540     }
541 
542     if (use_file) {
543 	/* Print to specified file. */
544 	fclose(f);
545 	return true;
546     }
547 
548     /* Print to printer. */
549 #if defined(_WIN32) /*[*/
550     fclose(f);
551     unlink(temp_name);
552     if (appres.interactive.do_confirms) {
553 	popup_an_info("Screen image printing.\n");
554     }
555 #endif /*]*/
556     Free(temp_name);
557     return true;
558 }
559 
560 /**
561  * Print screen module registration.
562  */
563 void
print_screen_register(void)564 print_screen_register(void)
565 {
566     static action_table_t print_text_actions[] = {
567 	{ AnPrintText,		PrintText_action,	ACTION_KE },
568     };
569 
570     /* Register the actions. */
571     register_actions(print_text_actions, array_count(print_text_actions));
572 }
573