1 /*
2  * $LynxId: HTFWriter.c,v 1.105 2013/07/21 00:41:24 tom Exp $
3  *
4  *		FILE WRITER				HTFWrite.h
5  *		===========
6  *
7  *	This version of the stream object just writes to a C file.
8  *	The file is assumed open and left open.
9  *
10  *	Bugs:
11  *		strings written must be less than buffer size.
12  */
13 
14 #define HTSTREAM_INTERNAL 1
15 
16 #include <HTUtils.h>
17 #include <LYCurses.h>
18 #include <HTFWriter.h>
19 #include <HTSaveToFile.h>
20 
21 #ifdef WIN_EX
22 #include <HTParse.h>
23 #endif
24 
25 #include <HTFormat.h>
26 #include <UCDefs.h>
27 #include <HTAlert.h>
28 #include <HTFile.h>
29 #include <HTInit.h>
30 #include <HTPlain.h>
31 
32 #include <LYStrings.h>
33 #include <LYUtils.h>
34 #include <LYGlobalDefs.h>
35 #include <LYClean.h>
36 #include <GridText.h>
37 #include <LYExtern.h>
38 #include <LYexit.h>
39 #include <LYLeaks.h>
40 #include <LYKeymap.h>
41 #include <LYGetFile.h>
42 #include <LYHistory.h>		/* store statusline messages */
43 
44 #ifdef USE_PERSISTENT_COOKIES
45 #include <LYCookie.h>
46 #endif
47 
48 /* contains the name of the temp file which is being downloaded into */
49 char *WWW_Download_File = NULL;
50 BOOLEAN LYCancelDownload = FALSE;	/* exported to HTFormat.c in libWWW */
51 
52 #ifdef VMS
53 static char *FIXED_RECORD_COMMAND = NULL;
54 
55 #ifdef USE_COMMAND_FILE		/* Keep this as an option. - FM    */
56 #define FIXED_RECORD_COMMAND_MASK "@Lynx_Dir:FIXED512 %s"
57 #else
58 #define FIXED_RECORD_COMMAND_MASK "%s"
59 static unsigned long LYVMS_FixedLengthRecords(char *filename);
60 #endif /* USE_COMMAND_FILE */
61 #endif /* VMS */
62 
63 HTStream *HTSaveToFile(HTPresentation *pres,
64 		       HTParentAnchor *anchor,
65 		       HTStream *sink);
66 
67 /*	Stream Object
68  *	-------------
69  */
70 struct _HTStream {
71     const HTStreamClass *isa;
72 
73     FILE *fp;			/* The file we've opened */
74     char *end_command;		/* What to do on _free.  */
75     char *remove_command;	/* What to do on _abort. */
76     char *viewer_command;	/* Saved external viewer */
77     HTFormat input_format;	/* Original pres->rep     */
78     HTFormat output_format;	/* Original pres->rep_out */
79     HTParentAnchor *anchor;	/* Original stream's anchor. */
80     HTStream *sink;		/* Original stream's sink.   */
81 #ifdef FNAMES_8_3
82     BOOLEAN idash;		/* remember position to become '.' */
83 #endif
84 };
85 
86 /*_________________________________________________________________________
87  *
88  *			A C T I O N	R O U T I N E S
89  *  Bug:
90  *	Most errors are ignored.
91  */
92 
93 /*	Error handling
94  *	------------------
95  */
HTFWriter_error(HTStream * me,const char * id)96 static void HTFWriter_error(HTStream *me, const char *id)
97 {
98     char buf[200];
99 
100     sprintf(buf, "%.60s: %.60s: %.60s",
101 	    id,
102 	    me->isa->name,
103 	    LYStrerror(errno));
104     HTAlert(buf);
105 /*
106  * Only disaster results from:
107  *	me->isa->_abort(me, NULL);
108  */
109 }
110 
111 /*	Character handling
112  *	------------------
113  */
HTFWriter_put_character(HTStream * me,int c)114 static void HTFWriter_put_character(HTStream *me, int c)
115 {
116     if (me->fp) {
117 	putc(c, me->fp);
118     }
119 }
120 
121 /*	String handling
122  *	---------------
123  */
HTFWriter_put_string(HTStream * me,const char * s)124 static void HTFWriter_put_string(HTStream *me, const char *s)
125 {
126     if (me->fp) {
127 	fputs(s, me->fp);
128     }
129 }
130 
131 /*	Buffer write.  Buffers can (and should!) be big.
132  *	------------
133  */
HTFWriter_write(HTStream * me,const char * s,int l)134 static void HTFWriter_write(HTStream *me, const char *s, int l)
135 {
136     size_t result;
137 
138     if (me->fp) {
139 	result = fwrite(s, (size_t) 1, (size_t) l, me->fp);
140 	if (result != (size_t) l) {
141 	    HTFWriter_error(me, "HTFWriter_write");
142 	}
143     }
144 }
145 
146 /*	Free an HTML object
147  *	-------------------
148  *
149  *	Note that the SGML parsing context is freed, but the created
150  *	object is not,
151  *	as it takes on an existence of its own unless explicitly freed.
152  */
HTFWriter_free(HTStream * me)153 static void HTFWriter_free(HTStream *me)
154 {
155     int len;
156     char *path = NULL;
157     char *addr = NULL;
158     BOOL use_zread = NO;
159     BOOLEAN found = FALSE;
160 
161 #ifdef WIN_EX
162     HANDLE cur_handle;
163 
164     cur_handle = GetForegroundWindow();
165 #endif
166 
167     if (me->fp)
168 	fflush(me->fp);
169     if (me->end_command) {	/* Temp file */
170 	LYCloseTempFP(me->fp);
171 #ifdef VMS
172 	if (0 == strcmp(me->end_command, "SaveVMSBinaryFile")) {
173 	    /*
174 	     * It's a binary file saved to disk on VMS, which
175 	     * we want to convert to fixed records format.  - FM
176 	     */
177 #ifdef USE_COMMAND_FILE
178 	    LYSystem(FIXED_RECORD_COMMAND);
179 #else
180 	    LYVMS_FixedLengthRecords(FIXED_RECORD_COMMAND);
181 #endif /* USE_COMMAND_FILE */
182 	    FREE(FIXED_RECORD_COMMAND);
183 
184 	    if (me->remove_command) {
185 		/* NEVER REMOVE THE FILE unless during an abort! */
186 		FREE(me->remove_command);
187 	    }
188 	} else
189 #endif /* VMS */
190 	if (me->input_format == HTAtom_for("www/compressed")) {
191 	    /*
192 	     * It's a compressed file supposedly cached to
193 	     * a temporary file for uncompression.  - FM
194 	     */
195 	    if (me->anchor->FileCache != NULL) {
196 		BOOL skip_loadfile = (BOOL) (me->viewer_command != NULL);
197 
198 		/*
199 		 * Save the path with the "gz" or "Z" suffix trimmed,
200 		 * and remove any previous uncompressed copy.  - FM
201 		 */
202 		StrAllocCopy(path, me->anchor->FileCache);
203 		if ((len = (int) strlen(path)) > 3 &&
204 		    (!strcasecomp(&path[len - 2], "gz") ||
205 		     !strcasecomp(&path[len - 2], "zz"))) {
206 #ifdef USE_ZLIB
207 		    if (!skip_loadfile) {
208 			use_zread = YES;
209 		    } else
210 #endif /* USE_ZLIB */
211 		    {
212 			path[len - 3] = '\0';
213 			(void) remove(path);
214 		    }
215 		} else if (len > 4 && !strcasecomp(&path[len - 3], "bz2")) {
216 #ifdef USE_BZLIB
217 		    if (!skip_loadfile) {
218 			use_zread = YES;
219 		    } else
220 #endif /* USE_BZLIB */
221 		    {
222 			path[len - 4] = '\0';
223 			(void) remove(path);
224 		    }
225 		} else if (len > 2 && !strcasecomp(&path[len - 1], "Z")) {
226 		    path[len - 2] = '\0';
227 		    (void) remove(path);
228 		}
229 		if (!use_zread) {
230 		    if (!dump_output_immediately) {
231 			/*
232 			 * Tell user what's happening.  - FM
233 			 */
234 			_HTProgress(me->end_command);
235 		    }
236 		    /*
237 		     * Uncompress it.  - FM
238 		     */
239 		    if (me->end_command && me->end_command[0])
240 			LYSystem(me->end_command);
241 		    found = LYCanReadFile(me->anchor->FileCache);
242 		}
243 		if (found) {
244 		    /*
245 		     * It's still there with the "gz" or "Z" suffix,
246 		     * so the uncompression failed.  - FM
247 		     */
248 		    if (!dump_output_immediately) {
249 			lynx_force_repaint();
250 			LYrefresh();
251 		    }
252 		    HTAlert(ERROR_UNCOMPRESSING_TEMP);
253 		    (void) LYRemoveTemp(me->anchor->FileCache);
254 		    FREE(me->anchor->FileCache);
255 		} else {
256 		    /*
257 		     * Succeeded!  Create a complete address
258 		     * for the uncompressed file and invoke
259 		     * HTLoadFile() to handle it.  - FM
260 		     */
261 #ifdef FNAMES_8_3
262 		    /*
263 		     * Assuming we have just uncompressed e.g.
264 		     * FILE-mpeg.gz -> FILE-mpeg, restore/shorten
265 		     * the name to be fit for passing to an external
266 		     * viewer, by renaming FILE-mpeg -> FILE.mpe - kw
267 		     */
268 		    if (skip_loadfile) {
269 			char *new_path = NULL;
270 			char *the_dash = me->idash ? strrchr(path, '-') : 0;
271 
272 			if (the_dash != 0) {
273 			    unsigned off = (the_dash - path);
274 
275 			    StrAllocCopy(new_path, path);
276 			    new_path[off] = '.';
277 			    if (strlen(new_path + off) > 4)
278 				new_path[off + 4] = '\0';
279 			    if (rename(path, new_path) == 0) {
280 				FREE(path);
281 				path = new_path;
282 			    } else {
283 				FREE(new_path);
284 			    }
285 			}
286 		    }
287 #endif /* FNAMES_8_3 */
288 		    LYLocalFileToURL(&addr, path);
289 		    if (!use_zread) {
290 			LYRenamedTemp(me->anchor->FileCache, path);
291 			StrAllocCopy(me->anchor->FileCache, path);
292 			StrAllocCopy(me->anchor->content_encoding, "binary");
293 		    }
294 		    FREE(path);
295 		    if (!skip_loadfile) {
296 			/*
297 			 * Lock the chartrans info we may possibly have,
298 			 * so HTCharsetFormat() will not apply the default
299 			 * for local files.  - KW
300 			 */
301 			if (HTAnchor_getUCLYhndl(me->anchor,
302 						 UCT_STAGE_PARSER) < 0) {
303 			    /*
304 			     * If not yet set - KW
305 			     */
306 			    HTAnchor_copyUCInfoStage(me->anchor,
307 						     UCT_STAGE_PARSER,
308 						     UCT_STAGE_MIME,
309 						     UCT_SETBY_DEFAULT + 1);
310 			}
311 			HTAnchor_copyUCInfoStage(me->anchor,
312 						 UCT_STAGE_PARSER,
313 						 UCT_STAGE_MIME, -1);
314 		    }
315 		    /*
316 		     * Create a complete address for
317 		     * the uncompressed file.  - FM
318 		     */
319 		    if (!dump_output_immediately) {
320 			/*
321 			 * Tell user what's happening.  - FM
322 			 * HTInfoMsg2(WWW_USING_MESSAGE, addr);
323 			 * but only in the history, not on screen -RS
324 			 */
325 			LYstore_message2(WWW_USING_MESSAGE, addr);
326 		    }
327 
328 		    if (skip_loadfile) {
329 			/*
330 			 * It's a temporary file we're passing to a viewer or
331 			 * helper application.  Loading the temp file through
332 			 * HTLoadFile() would result in yet another HTStream
333 			 * (created with HTSaveAndExecute()) which would just
334 			 * copy the temp file to another temp file (or even the
335 			 * same!).  We can skip this needless duplication by
336 			 * using the viewer_command which has already been
337 			 * determined when the HTCompressed stream was created.
338 			 * - kw
339 			 */
340 			FREE(me->end_command);
341 
342 			HTAddParam(&(me->end_command), me->viewer_command, 1, me->anchor->FileCache);
343 			HTEndParam(&(me->end_command), me->viewer_command, 1);
344 
345 			if (!dump_output_immediately) {
346 			    /*
347 			     * Tell user what's happening.  - FM
348 			     */
349 			    HTProgress(me->end_command);
350 #ifndef WIN_EX
351 			    stop_curses();
352 #endif
353 			}
354 #ifdef _WIN_CC
355 			exec_command(me->end_command, FALSE);
356 #else
357 			LYSystem(me->end_command);
358 #endif
359 			if (me->remove_command) {
360 			    /* NEVER REMOVE THE FILE unless during an abort!!! */
361 			    FREE(me->remove_command);
362 			}
363 			if (!dump_output_immediately) {
364 #ifdef WIN_EX
365 			    if (focus_window) {
366 				HTInfoMsg(gettext("Set focus1"));
367 				(void) SetForegroundWindow(cur_handle);
368 			    }
369 #else
370 			    start_curses();
371 #endif
372 			}
373 		    } else {
374 			(void) HTLoadFile(addr,
375 					  me->anchor,
376 					  me->output_format,
377 					  me->sink);
378 		    }
379 		    if (dump_output_immediately &&
380 			me->output_format == HTAtom_for("www/present")) {
381 			FREE(addr);
382 			(void) remove(me->anchor->FileCache);
383 			FREE(me->anchor->FileCache);
384 			FREE(me->remove_command);
385 			FREE(me->end_command);
386 			FREE(me->viewer_command);
387 			FREE(me);
388 			return;
389 		    }
390 		}
391 		FREE(addr);
392 	    }
393 	    if (me->remove_command) {
394 		/* NEVER REMOVE THE FILE unless during an abort!!! */
395 		FREE(me->remove_command);
396 	    }
397 	} else if (strcmp(me->end_command, "SaveToFile")) {
398 	    /*
399 	     * It's a temporary file we're passing to a viewer or helper
400 	     * application.  - FM
401 	     */
402 	    if (!dump_output_immediately) {
403 		/*
404 		 * Tell user what's happening.  - FM
405 		 */
406 		_HTProgress(me->end_command);
407 #ifndef WIN_EX
408 		stop_curses();
409 #endif
410 	    }
411 #ifdef _WIN_CC
412 	    exec_command(me->end_command, wait_viewer_termination);
413 #else
414 	    LYSystem(me->end_command);
415 #endif
416 
417 	    if (me->remove_command) {
418 		/* NEVER REMOVE THE FILE unless during an abort!!! */
419 		FREE(me->remove_command);
420 	    }
421 	    if (!dump_output_immediately) {
422 #ifdef WIN_EX
423 		if (focus_window) {
424 		    HTInfoMsg(gettext("Set focus2"));
425 		    (void) SetForegroundWindow(cur_handle);
426 		}
427 #else
428 		start_curses();
429 #endif
430 	    }
431 	} else {
432 	    /*
433 	     * It's a file we saved to disk for handling via a menu.  - FM
434 	     */
435 	    if (me->remove_command) {
436 		/* NEVER REMOVE THE FILE unless during an abort!!! */
437 		FREE(me->remove_command);
438 	    }
439 	    if (!dump_output_immediately) {
440 #ifdef WIN_EX
441 		if (focus_window) {
442 		    HTInfoMsg(gettext("Set focus3"));
443 		    (void) SetForegroundWindow(cur_handle);
444 		}
445 #else
446 		start_curses();
447 #endif
448 	    }
449 	}
450 	FREE(me->end_command);
451     }
452     FREE(me->viewer_command);
453 
454     if (dump_output_immediately) {
455 	if (me->anchor->FileCache)
456 	    (void) remove(me->anchor->FileCache);
457 	FREE(me);
458 #ifdef USE_PERSISTENT_COOKIES
459 	/*
460 	 * We want to save cookies picked up when in source mode.  ...
461 	 */
462 	if (persistent_cookies)
463 	    LYStoreCookies(LYCookieSaveFile);
464 #endif /* USE_PERSISTENT_COOKIES */
465 	exit_immediately(EXIT_SUCCESS);
466     }
467 
468     FREE(me);
469     return;
470 }
471 
472 #ifdef VMS
473 #  define REMOVE_COMMAND "delete/noconfirm/nolog %s;"
474 #else
475 #  define REMOVE_COMMAND "%s"
476 #endif /* VMS */
477 
478 /*	Abort writing
479  *	-------------
480  */
HTFWriter_abort(HTStream * me,HTError e GCC_UNUSED)481 static void HTFWriter_abort(HTStream *me, HTError e GCC_UNUSED)
482 {
483     CTRACE((tfp, "HTFWriter_abort called\n"));
484     LYCloseTempFP(me->fp);
485     FREE(me->viewer_command);
486     if (me->end_command) {	/* Temp file */
487 	CTRACE((tfp, "HTFWriter: Aborting: file not executed or saved.\n"));
488 	FREE(me->end_command);
489 	if (me->remove_command) {
490 #ifdef VMS
491 	    LYSystem(me->remove_command);
492 #else
493 	    (void) chmod(me->remove_command, 0600);	/* Ignore errors */
494 	    if (0 != unlink(me->remove_command)) {
495 		char buf[560];
496 
497 		sprintf(buf, "%.60s '%.400s': %.60s",
498 			gettext("Error deleting file"),
499 			me->remove_command, LYStrerror(errno));
500 		HTAlert(buf);
501 	    }
502 #endif
503 	    FREE(me->remove_command);
504 	}
505     }
506 
507     FREE(WWW_Download_File);
508 
509     FREE(me);
510 }
511 
512 /*	Structured Object Class
513  *	-----------------------
514  */
515 static const HTStreamClass HTFWriter =	/* As opposed to print etc */
516 {
517     "FileWriter",
518     HTFWriter_free,
519     HTFWriter_abort,
520     HTFWriter_put_character,
521     HTFWriter_put_string,
522     HTFWriter_write
523 };
524 
525 /*	Subclass-specific Methods
526  *	-------------------------
527  */
HTFWriter_new(FILE * fp)528 HTStream *HTFWriter_new(FILE *fp)
529 {
530     HTStream *me;
531 
532     if (!fp)
533 	return NULL;
534 
535     me = typecalloc(HTStream);
536     if (me == NULL)
537 	outofmem(__FILE__, "HTFWriter_new");
538 
539     assert(me != NULL);
540 
541     me->isa = &HTFWriter;
542 
543     me->fp = fp;
544     me->end_command = NULL;
545     me->remove_command = NULL;
546     me->anchor = NULL;
547     me->sink = NULL;
548 
549     return me;
550 }
551 
552 /*	Make system command from template
553  *	---------------------------------
554  *
555  *	See mailcap spec for description of template.
556  */
mailcap_substitute(HTParentAnchor * anchor,HTPresentation * pres,char * fnam)557 static char *mailcap_substitute(HTParentAnchor *anchor,
558 				HTPresentation *pres,
559 				char *fnam)
560 {
561     char *result = LYMakeMailcapCommand(pres->command,
562 					anchor->content_type_params,
563 					fnam);
564 
565 #if defined(UNIX)
566     /* if we don't have a "%s" token, expect to provide the file via stdin */
567     if (!LYMailcapUsesPctS(pres->command)) {
568 	char *prepend = 0;
569 	const char *format = "( %s ) < %s";
570 
571 	HTSprintf(&prepend, "( %s", result);	/* ...avoid quoting */
572 	HTAddParam(&prepend, format, 2, fnam);	/* ...to quote if needed */
573 	FREE(result);
574 	result = prepend;
575     }
576 #endif
577     return result;
578 }
579 
580 /*	Take action using a system command
581  *	----------------------------------
582  *
583  *	originally from Ghostview handling by Marc Andreseen.
584  *	Creates temporary file, writes to it, executes system command
585  *	on end-document.  The suffix of the temp file can be given
586  *	in case the application is fussy, or so that a generic opener can
587  *	be used.
588  */
HTSaveAndExecute(HTPresentation * pres,HTParentAnchor * anchor,HTStream * sink)589 HTStream *HTSaveAndExecute(HTPresentation *pres,
590 			   HTParentAnchor *anchor,
591 			   HTStream *sink)
592 {
593     char fnam[LY_MAXPATH];
594     const char *suffix;
595     HTStream *me;
596 
597     if (traversal) {
598 	LYCancelledFetch = TRUE;
599 	return (NULL);
600     }
601 #if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)
602     if (pres->quality >= 999.0) {	/* exec link */
603 	if (dump_output_immediately) {
604 	    LYCancelledFetch = TRUE;
605 	    return (NULL);
606 	}
607 	if (no_exec) {
608 	    HTAlert(EXECUTION_DISABLED);
609 	    return HTPlainPresent(pres, anchor, sink);
610 	}
611 	if (!local_exec) {
612 	    if (local_exec_on_local_files &&
613 		(LYJumpFileURL ||
614 		 !StrNCmp(anchor->address, "file://localhost", 16))) {
615 		/* allow it to continue */
616 		;
617 	    } else {
618 		char *buf = 0;
619 
620 		HTSprintf0(&buf, EXECUTION_DISABLED_FOR_FILE,
621 			   key_for_func(LYK_OPTIONS));
622 		HTAlert(buf);
623 		FREE(buf);
624 		return HTPlainPresent(pres, anchor, sink);
625 	    }
626 	}
627     }
628 #endif /* EXEC_LINKS || EXEC_SCRIPTS */
629 
630     if (dump_output_immediately) {
631 	return (HTSaveToFile(pres, anchor, sink));
632     }
633 
634     me = typecalloc(HTStream);
635     if (me == NULL)
636 	outofmem(__FILE__, "HTSaveAndExecute");
637 
638     assert(me != NULL);
639 
640     me->isa = &HTFWriter;
641     me->input_format = pres->rep;
642     me->output_format = pres->rep_out;
643     me->anchor = anchor;
644     me->sink = sink;
645 
646     if (LYCachedTemp(fnam, &(anchor->FileCache))) {
647 	/* This used to be LYNewBinFile(fnam); changed to a different call so
648 	 * that the open fp gets registered in the list keeping track of temp
649 	 * files, equivalent to when LYOpenTemp() gets called below.  This
650 	 * avoids a file descriptor leak caused by LYCloseTempFP() not being
651 	 * able to find the fp.  The binary suffix is expected to not be used,
652 	 * it's only for fallback in unusual error cases.  - kw
653 	 */
654 	me->fp = LYOpenTempRewrite(fnam, BIN_SUFFIX, BIN_W);
655     } else {
656 #if defined(WIN_EX) && !defined(__CYGWIN__)	/* 1998/01/04 (Sun) */
657 	if (!StrNCmp(anchor->address, "file://localhost", 16)) {
658 
659 	    /* 1998/01/23 (Fri) 17:38:26 */
660 	    char *cp, *view_fname;
661 
662 	    me->fp = NULL;
663 
664 	    view_fname = fnam + 3;
665 	    LYStrNCpy(view_fname, anchor->address + 17, sizeof(fnam) - 5);
666 	    HTUnEscape(view_fname);
667 
668 	    if (strchr(view_fname, ':') == NULL) {
669 		fnam[0] = windows_drive[0];
670 		fnam[1] = windows_drive[1];
671 		fnam[2] = '/';
672 		view_fname = fnam;
673 	    }
674 
675 	    /* 1998/04/21 (Tue) 11:04:16 */
676 	    cp = view_fname;
677 	    while (*cp) {
678 		if (IS_SJIS_HI1(UCH(*cp)) || IS_SJIS_HI2(UCH(*cp))) {
679 		    cp += 2;
680 		    continue;
681 		} else if (*cp == '/') {
682 		    *cp = '\\';
683 		}
684 		cp++;
685 	    }
686 	    if (strchr(view_fname, ' '))
687 		view_fname = quote_pathname(view_fname);
688 
689 	    StrAllocCopy(me->viewer_command, pres->command);
690 
691 	    me->end_command = mailcap_substitute(anchor, pres, view_fname);
692 	    me->remove_command = NULL;
693 
694 	    return me;
695 	}
696 #endif
697 	/*
698 	 * Check for a suffix.
699 	 * Save the file under a suitably suffixed name.
700 	 */
701 	if (!strcasecomp(pres->rep->name, "text/html")) {
702 	    suffix = HTML_SUFFIX;
703 	} else if (!strncasecomp(pres->rep->name, "text/", 5)) {
704 	    suffix = TEXT_SUFFIX;
705 	} else if ((suffix = HTFileSuffix(pres->rep,
706 					  anchor->content_encoding)) == 0
707 		   || *suffix != '.') {
708 	    if (!strncasecomp(pres->rep->name, "application/", 12)) {
709 		suffix = BIN_SUFFIX;
710 	    } else {
711 		suffix = HTML_SUFFIX;
712 	    }
713 	}
714 	me->fp = LYOpenTemp(fnam, suffix, BIN_W);
715     }
716 
717     if (!me->fp) {
718 	HTAlert(CANNOT_OPEN_TEMP);
719 	FREE(me);
720 	return NULL;
721     }
722 
723     StrAllocCopy(me->viewer_command, pres->command);
724     /*
725      * Make command to process file.
726      */
727     me->end_command = mailcap_substitute(anchor, pres, fnam);
728 
729     /*
730      * Make command to delete file.
731      */
732     me->remove_command = 0;
733     HTAddParam(&(me->remove_command), REMOVE_COMMAND, 1, fnam);
734     HTEndParam(&(me->remove_command), REMOVE_COMMAND, 1);
735 
736     StrAllocCopy(anchor->FileCache, fnam);
737     return me;
738 }
739 
740 /*	Format Converter using system command
741  *	-------------------------------------
742  */
743 
744 /* @@@@@@@@@@@@@@@@@@@@@@ */
745 
746 /*	Save to a local file   LJM!!!
747  *	--------------------
748  *
749  *	usually a binary file that can't be displayed
750  *
751  *	originally from Ghostview handling by Marc Andreseen.
752  *	Asks the user if he wants to continue, creates a temporary
753  *	file, and writes to it.  In HTSaveToFile_Free
754  *	the user will see a list of choices for download
755  */
HTSaveToFile(HTPresentation * pres,HTParentAnchor * anchor,HTStream * sink)756 HTStream *HTSaveToFile(HTPresentation *pres,
757 		       HTParentAnchor *anchor,
758 		       HTStream *sink)
759 {
760     HTStream *ret_obj;
761     char fnam[LY_MAXPATH];
762     const char *suffix;
763     char *cp;
764     int c = 0;
765 
766 #ifdef VMS
767     BOOL IsBinary = TRUE;
768 #endif
769 
770     ret_obj = typecalloc(HTStream);
771 
772     if (ret_obj == NULL)
773 	outofmem(__FILE__, "HTSaveToFile");
774 
775     assert(ret_obj != NULL);
776 
777     ret_obj->isa = &HTFWriter;
778     ret_obj->remove_command = NULL;
779     ret_obj->end_command = NULL;
780     ret_obj->input_format = pres->rep;
781     ret_obj->output_format = pres->rep_out;
782     ret_obj->anchor = anchor;
783     ret_obj->sink = sink;
784 
785     if (dump_output_immediately) {
786 	ret_obj->fp = stdout;	/* stdout */
787 	if (HTOutputFormat == HTAtom_for("www/download"))
788 	    goto Prepend_BASE;
789 	return ret_obj;
790     }
791 
792     LYCancelDownload = FALSE;
793     if (HTOutputFormat != HTAtom_for("www/download")) {
794 	if (traversal ||
795 	    (no_download && !override_no_download && no_disk_save)) {
796 	    if (!traversal) {
797 		HTAlert(CANNOT_DISPLAY_FILE);
798 	    }
799 	    LYCancelDownload = TRUE;
800 	    if (traversal)
801 		LYCancelledFetch = TRUE;
802 	    FREE(ret_obj);
803 	    return (NULL);
804 	}
805 
806 	if (((cp = strchr(pres->rep->name, ';')) != NULL) &&
807 	    strstr((cp + 1), "charset") != NULL) {
808 	    _user_message(MSG_DOWNLOAD_OR_CANCEL, pres->rep->name);
809 	} else if (*(pres->rep->name) != '\0') {
810 	    _user_message(MSG_DOWNLOAD_OR_CANCEL, pres->rep->name);
811 	} else {
812 	    _statusline(CANNOT_DISPLAY_FILE_D_OR_C);
813 	}
814 
815 	while (c != 'D' && c != 'C' && !LYCharIsINTERRUPT(c)) {
816 	    c = LYgetch_single();
817 #ifdef VMS
818 	    /*
819 	     * 'C'ancel on Control-C or Control-Y and
820 	     * a 'N'o to the "really exit" query.  - FM
821 	     */
822 	    if (HadVMSInterrupt) {
823 		HadVMSInterrupt = FALSE;
824 		c = 'C';
825 	    }
826 #endif /* VMS */
827 	}
828 
829 	/*
830 	 * Cancel on 'C', 'c' or Control-G or Control-C.
831 	 */
832 	if (c == 'C' || LYCharIsINTERRUPT(c)) {
833 	    _statusline(CANCELLING_FILE);
834 	    LYCancelDownload = TRUE;
835 	    FREE(ret_obj);
836 	    return (NULL);
837 	}
838     }
839 
840     /*
841      * Set up a 'D'ownload.
842      */
843     if (LYCachedTemp(fnam, &(anchor->FileCache))) {
844 	/* This used to be LYNewBinFile(fnam); changed to a different call so
845 	 * that the open fp gets registered in the list keeping track of temp
846 	 * files, equivalent to when LYOpenTemp() gets called below.  This
847 	 * avoids a file descriptor leak caused by LYCloseTempFP() not being
848 	 * able to find the fp.  The binary suffix is expected to not be used,
849 	 * it's only for fallback in unusual error cases.  - kw
850 	 */
851 	ret_obj->fp = LYOpenTempRewrite(fnam, BIN_SUFFIX, BIN_W);
852     } else {
853 	/*
854 	 * Check for a suffix.
855 	 * Save the file under a suitably suffixed name.
856 	 */
857 	if (!strcasecomp(pres->rep->name, "text/html")) {
858 	    suffix = HTML_SUFFIX;
859 	} else if (!strncasecomp(pres->rep->name, "text/", 5)) {
860 	    suffix = TEXT_SUFFIX;
861 	} else if (!strncasecomp(pres->rep->name, "application/", 12)) {
862 	    suffix = BIN_SUFFIX;
863 	} else if ((suffix = HTFileSuffix(pres->rep,
864 					  anchor->content_encoding)) == 0
865 		   || *suffix != '.') {
866 	    suffix = HTML_SUFFIX;
867 	}
868 	ret_obj->fp = LYOpenTemp(fnam, suffix, BIN_W);
869     }
870 
871     if (!ret_obj->fp) {
872 	HTAlert(CANNOT_OPEN_OUTPUT);
873 	FREE(ret_obj);
874 	return NULL;
875     }
876 
877     if (0 == strncasecomp(pres->rep->name, "text/", 5) ||
878 	0 == strcasecomp(pres->rep->name, "application/postscript") ||
879 	0 == strcasecomp(pres->rep->name, "application/x-RUNOFF-MANUAL"))
880 	/*
881 	 * It's a text file requested via 'd'ownload.  Keep adding others to
882 	 * the above list, 'til we add a configurable procedure.  - FM
883 	 */
884 #ifdef VMS
885 	IsBinary = FALSE;
886 #endif
887 
888     /*
889      * Any "application/foo" or other non-"text/foo" types that are actually
890      * text but not checked, above, will be treated as binary, so show the type
891      * to help sort that out later.  Unix folks don't need to know this, but
892      * we'll show it to them, too.  - FM
893      */
894     HTInfoMsg2(CONTENT_TYPE_MSG, pres->rep->name);
895 
896     StrAllocCopy(WWW_Download_File, fnam);
897 
898     /*
899      * Make command to delete file.
900      */
901     ret_obj->remove_command = 0;
902     HTAddParam(&(ret_obj->remove_command), REMOVE_COMMAND, 1, fnam);
903     HTEndParam(&(ret_obj->remove_command), REMOVE_COMMAND, 1);
904 
905 #ifdef VMS
906     if (IsBinary && UseFixedRecords) {
907 	StrAllocCopy(ret_obj->end_command, "SaveVMSBinaryFile");
908 	FIXED_RECORD_COMMAND = 0;
909 	HTAddParam(&FIXED_RECORD_COMMAND, FIXED_RECORD_COMMAND_MASK, 1, fnam);
910 	HTEndParam(&FIXED_RECORD_COMMAND, FIXED_RECORD_COMMAND_MASK, 1);
911 
912     } else {
913 #endif /* VMS */
914 	StrAllocCopy(ret_obj->end_command, "SaveToFile");
915 #ifdef VMS
916     }
917 #endif /* VMS */
918 
919     _statusline(RETRIEVING_FILE);
920 
921     StrAllocCopy(anchor->FileCache, fnam);
922   Prepend_BASE:
923     if (LYPrependBaseToSource &&
924 	!strncasecomp(pres->rep->name, "text/html", 9) &&
925 	!anchor->content_encoding) {
926 	/*
927 	 * Add the document's base as a BASE tag at the top of the file, so
928 	 * that any partial or relative URLs within it will be resolved
929 	 * relative to that if no BASE tag is present and replaces it.  Note
930 	 * that the markup will be technically invalid if a DOCTYPE
931 	 * declaration, or HTML or HEAD tags, are present, and thus the file
932 	 * may need editing for perfection.  - FM
933 	 *
934 	 * Add timestamp (last reload).
935 	 */
936 	char *temp = NULL;
937 
938 	if (non_empty(anchor->content_base)) {
939 	    StrAllocCopy(temp, anchor->content_base);
940 	} else if (non_empty(anchor->content_location)) {
941 	    StrAllocCopy(temp, anchor->content_location);
942 	}
943 	if (temp) {
944 	    LYRemoveBlanks(temp);
945 	    if (!is_url(temp)) {
946 		FREE(temp);
947 	    }
948 	}
949 
950 	fprintf(ret_obj->fp,
951 		"<!-- X-URL: %s -->\n", anchor->address);
952 	if (non_empty(anchor->date)) {
953 	    fprintf(ret_obj->fp,
954 		    "<!-- Date: %s -->\n", anchor->date);
955 	    if (non_empty(anchor->last_modified)
956 		&& strcmp(anchor->last_modified, anchor->date)
957 		&& strcmp(anchor->last_modified,
958 			  "Thu, 01 Jan 1970 00:00:01 GMT")) {
959 		fprintf(ret_obj->fp,
960 			"<!-- Last-Modified: %s -->\n", anchor->last_modified);
961 	    }
962 	}
963 	fprintf(ret_obj->fp,
964 		"<BASE HREF=\"%s\">\n\n", (temp ? temp : anchor->address));
965 	FREE(temp);
966     }
967     if (LYPrependCharsetToSource &&
968 	!strncasecomp(pres->rep->name, "text/html", 9) &&
969 	!anchor->content_encoding) {
970 	/*
971 	 * Add the document's charset as a META CHARSET tag at the top of the
972 	 * file, so HTTP charset header will not be forgotten when a document
973 	 * saved as local file.  We add this line only(!) if HTTP charset
974 	 * present.  - LP Note that the markup will be technically invalid if a
975 	 * DOCTYPE declaration, or HTML or HEAD tags, are present, and thus the
976 	 * file may need editing for perfection.  - FM
977 	 */
978 	char *temp = NULL;
979 
980 	if (non_empty(anchor->charset)) {
981 	    StrAllocCopy(temp, anchor->charset);
982 	    LYRemoveBlanks(temp);
983 	    fprintf(ret_obj->fp,
984 		    "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=%s\">\n\n",
985 		    temp);
986 	}
987 	FREE(temp);
988     }
989     return ret_obj;
990 }
991 
992 /*	Set up stream for uncompressing - FM
993  *	-------------------------------
994  */
HTCompressed(HTPresentation * pres,HTParentAnchor * anchor,HTStream * sink)995 HTStream *HTCompressed(HTPresentation *pres,
996 		       HTParentAnchor *anchor,
997 		       HTStream *sink)
998 {
999     HTStream *me;
1000     HTFormat format;
1001     char *type = NULL;
1002     HTPresentation *Pres = NULL;
1003     HTPresentation *Pnow = NULL;
1004     int n, i;
1005     BOOL can_present = FALSE;
1006     char fnam[LY_MAXPATH];
1007     char temp[LY_MAXPATH];	/* actually stores just a suffix */
1008     const char *suffix;
1009     char *uncompress_mask = NULL;
1010     const char *compress_suffix = "";
1011     const char *middle;
1012 
1013     /*
1014      * Deal with any inappropriate invocations of this function, or a download
1015      * request, in which case we won't bother to uncompress the file.  - FM
1016      */
1017     if (!(anchor->content_encoding && anchor->content_type)) {
1018 	/*
1019 	 * We have no idea what we're dealing with, so treat it as a binary
1020 	 * stream.  - FM
1021 	 */
1022 	format = HTAtom_for("application/octet-stream");
1023 	me = HTStreamStack(format, pres->rep_out, sink, anchor);
1024 	return me;
1025     }
1026     n = HTList_count(HTPresentations);
1027     for (i = 0; i < n; i++) {
1028 	Pnow = (HTPresentation *) HTList_objectAt(HTPresentations, i);
1029 	if (!strcasecomp(Pnow->rep->name, anchor->content_type) &&
1030 	    Pnow->rep_out == WWW_PRESENT) {
1031 	    const char *program = "";
1032 
1033 	    /*
1034 	     * Pick the best presentation.  User-defined mappings are at the
1035 	     * end of the list, and unless the quality is lower, we prefer
1036 	     * those.
1037 	     */
1038 	    if (Pres == 0)
1039 		Pres = Pnow;
1040 	    else if (Pres->quality > Pnow->quality)
1041 		continue;
1042 	    else
1043 		Pres = Pnow;
1044 	    /*
1045 	     * We have a presentation mapping for it.  - FM
1046 	     */
1047 	    can_present = TRUE;
1048 	    switch (HTEncodingToCompressType(anchor->content_encoding)) {
1049 	    case cftGzip:
1050 		if ((program = HTGetProgramPath(ppGZIP)) != NULL) {
1051 		    /*
1052 		     * It's compressed with the modern gzip.  - FM
1053 		     */
1054 		    StrAllocCopy(uncompress_mask, program);
1055 		    StrAllocCat(uncompress_mask, " -d --no-name %s");
1056 		    compress_suffix = "gz";
1057 		}
1058 		break;
1059 	    case cftDeflate:
1060 		if ((program = HTGetProgramPath(ppINFLATE)) != NULL) {
1061 		    /*
1062 		     * It's compressed with a zlib wrapper.
1063 		     */
1064 		    StrAllocCopy(uncompress_mask, program);
1065 		    StrAllocCat(uncompress_mask, " %s");
1066 		    compress_suffix = "zz";
1067 		}
1068 		break;
1069 	    case cftBzip2:
1070 		if ((program = HTGetProgramPath(ppBZIP2)) != NULL) {
1071 		    StrAllocCopy(uncompress_mask, program);
1072 		    StrAllocCat(uncompress_mask, " -d %s");
1073 		    compress_suffix = "bz2";
1074 		}
1075 		break;
1076 	    case cftCompress:
1077 		if ((program = HTGetProgramPath(ppUNCOMPRESS)) != NULL) {
1078 		    /*
1079 		     * It's compressed the old fashioned Unix way.  - FM
1080 		     */
1081 		    StrAllocCopy(uncompress_mask, program);
1082 		    StrAllocCat(uncompress_mask, " %s");
1083 		    compress_suffix = "Z";
1084 		}
1085 		break;
1086 	    case cftNone:
1087 		break;
1088 	    }
1089 	}
1090     }
1091     if (can_present == FALSE ||	/* no presentation mapping */
1092 	uncompress_mask == NULL ||	/* not gzip or compress */
1093 	strchr(anchor->content_type, ';') ||	/* wrong charset */
1094 	HTOutputFormat == HTAtom_for("www/download") ||		/* download */
1095 	!strcasecomp(pres->rep_out->name, "www/download") ||	/* download */
1096 	(traversal &&		/* only handle html or plain text for traversals */
1097 	 strcasecomp(anchor->content_type, "text/html") &&
1098 	 strcasecomp(anchor->content_type, "text/plain"))) {
1099 	/*
1100 	 * Cast the Content-Encoding to a Content-Type and pass it back to be
1101 	 * handled as that type.  - FM
1102 	 */
1103 	if (strchr(anchor->content_encoding, '/') == NULL) {
1104 	    /*
1105 	     * Use "x-" prefix, none of the types we are likely to construct
1106 	     * here are official.  That is we generate "application/x-gzip" and
1107 	     * so on.  - kw
1108 	     */
1109 	    if (!strncasecomp(anchor->content_encoding, "x-", 2))
1110 		StrAllocCopy(type, "application/");
1111 	    else
1112 		StrAllocCopy(type, "application/x-");
1113 	    StrAllocCat(type, anchor->content_encoding);
1114 	} else {
1115 	    StrAllocCopy(type, anchor->content_encoding);
1116 	}
1117 	format = HTAtom_for(type);
1118 	FREE(type);
1119 	FREE(uncompress_mask);
1120 	me = HTStreamStack(format, pres->rep_out, sink, anchor);
1121 	return me;
1122     }
1123 
1124     /*
1125      * Set up the stream structure for uncompressing and then handling based on
1126      * the uncompressed Content-Type.- FM
1127      */
1128     me = typecalloc(HTStream);
1129     if (me == NULL)
1130 	outofmem(__FILE__, "HTCompressed");
1131 
1132     assert(me != NULL);
1133 
1134     me->isa = &HTFWriter;
1135     me->input_format = pres->rep;
1136     me->output_format = pres->rep_out;
1137     me->anchor = anchor;
1138     me->sink = sink;
1139 #ifdef FNAMES_8_3
1140     me->idash = FALSE;
1141 #endif
1142 
1143     /*
1144      * Remove any old versions of the file.  - FM
1145      */
1146     if (anchor->FileCache) {
1147 	(void) LYRemoveTemp(anchor->FileCache);
1148 	FREE(anchor->FileCache);
1149     }
1150 
1151     /*
1152      * Get a new temporary filename and substitute a suitable suffix.  - FM
1153      */
1154     middle = NULL;
1155     if (!strcasecomp(anchor->content_type, "text/html")) {
1156 	middle = HTML_SUFFIX;
1157 	middle++;		/* point to 'h' of .htm(l) - kw */
1158     } else if (!strncasecomp(anchor->content_type, "text/", 5)) {
1159 	middle = TEXT_SUFFIX + 1;
1160     } else if (!strncasecomp(anchor->content_type, "application/", 12)) {
1161 	/* FIXME: why is this BEFORE HTFileSuffix? */
1162 	middle = BIN_SUFFIX + 1;
1163     } else if ((suffix =
1164 		HTFileSuffix(HTAtom_for(anchor->content_type), NULL)) &&
1165 	       *suffix == '.') {
1166 #if defined(VMS) || defined(FNAMES_8_3)
1167 	if (strchr(suffix + 1, '.') == NULL)
1168 #endif
1169 	    middle = suffix + 1;
1170     }
1171 
1172     temp[0] = 0;		/* construct the suffix */
1173     if (middle) {
1174 #ifdef FNAMES_8_3
1175 	me->idash = TRUE;	/* remember position of '-'  - kw */
1176 	strcat(temp, "-");	/* NAME-htm,  NAME-txt, etc. - hack for DOS */
1177 #else
1178 	strcat(temp, ".");	/* NAME.html, NAME-txt etc. */
1179 #endif /* FNAMES_8_3 */
1180 	strcat(temp, middle);
1181 #ifdef VMS
1182 	strcat(temp, "-");	/* NAME.html-gz, NAME.txt-gz, NAME.txt-Z etc. */
1183 #else
1184 	strcat(temp, ".");	/* NAME-htm.gz (DOS), NAME.html.gz (UNIX)etc. */
1185 #endif /* VMS */
1186     }
1187     strcat(temp, compress_suffix);
1188 
1189     /*
1190      * Open the file for receiving the compressed input stream.  - FM
1191      */
1192     me->fp = LYOpenTemp(fnam, temp, BIN_W);
1193     if (!me->fp) {
1194 	HTAlert(CANNOT_OPEN_TEMP);
1195 	FREE(uncompress_mask);
1196 	FREE(me);
1197 	return NULL;
1198     }
1199 
1200     /*
1201      * me->viewer_command will be NULL if the converter Pres found above is not
1202      * for an external viewer but an internal HTStream converter.  We also
1203      * don't set it under conditions where HTSaveAndExecute would disallow
1204      * execution of the command.  - KW
1205      */
1206     if (!dump_output_immediately && !traversal
1207 #if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)
1208 	&& (Pres->quality < 999.0 ||
1209 	    (!no_exec &&	/* allowed exec link or script ? */
1210 	     (local_exec ||
1211 	      (local_exec_on_local_files &&
1212 	       (LYJumpFileURL ||
1213 		!StrNCmp(anchor->address, "file://localhost", 16))))))
1214 #endif /* EXEC_LINKS || EXEC_SCRIPTS */
1215 	) {
1216 	StrAllocCopy(me->viewer_command, Pres->command);
1217     }
1218 
1219     /*
1220      * Make command to process file.  - FM
1221      */
1222 #ifdef USE_BZLIB
1223     if (compress_suffix[0] == 'b'	/* must be bzip2 */
1224 	&& !me->viewer_command) {
1225 	/*
1226 	 * We won't call bzip2 externally, so we don't need to supply a command
1227 	 * for it.
1228 	 */
1229 	StrAllocCopy(me->end_command, "");
1230     } else
1231 #endif
1232 #ifdef USE_ZLIB
1233 	/* FIXME: allow deflate here, e.g., 'z' */
1234 	if (compress_suffix[0] == 'g'	/* must be gzip */
1235 	    && !me->viewer_command) {
1236 	/*
1237 	 * We won't call gzip or compress externally, so we don't need to
1238 	 * supply a command for it.
1239 	 */
1240 	StrAllocCopy(me->end_command, "");
1241     } else
1242 #endif /* USE_ZLIB */
1243     {
1244 	me->end_command = 0;
1245 	HTAddParam(&(me->end_command), uncompress_mask, 1, fnam);
1246 	HTEndParam(&(me->end_command), uncompress_mask, 1);
1247     }
1248     FREE(uncompress_mask);
1249 
1250     /*
1251      * Make command to delete file.  - FM
1252      */
1253     me->remove_command = 0;
1254     HTAddParam(&(me->remove_command), REMOVE_COMMAND, 1, fnam);
1255     HTEndParam(&(me->remove_command), REMOVE_COMMAND, 1);
1256 
1257     /*
1258      * Save the filename and return the structure.  - FM
1259      */
1260     StrAllocCopy(anchor->FileCache, fnam);
1261     return me;
1262 }
1263 
1264 /*	Dump output to stdout - LJM & FM
1265  *	---------------------
1266  *
1267  */
HTDumpToStdout(HTPresentation * pres GCC_UNUSED,HTParentAnchor * anchor,HTStream * sink GCC_UNUSED)1268 HTStream *HTDumpToStdout(HTPresentation *pres GCC_UNUSED,
1269 			 HTParentAnchor *anchor,
1270 			 HTStream *sink GCC_UNUSED)
1271 {
1272     HTStream *ret_obj;
1273 
1274     ret_obj = typecalloc(HTStream);
1275 
1276     if (ret_obj == NULL)
1277 	outofmem(__FILE__, "HTDumpToStdout");
1278 
1279     assert(ret_obj != NULL);
1280 
1281     ret_obj->isa = &HTFWriter;
1282     ret_obj->remove_command = NULL;
1283     ret_obj->end_command = NULL;
1284     ret_obj->anchor = anchor;
1285 
1286     ret_obj->fp = stdout;	/* stdout */
1287     return ret_obj;
1288 }
1289 
1290 #if defined(VMS) && !defined(USE_COMMAND_FILE)
1291 #include <fab.h>
1292 #include <rmsdef.h>		/* RMS status codes */
1293 #include <iodef.h>		/* I/O function codes */
1294 #include <fibdef.h>		/* file information block defs */
1295 #include <atrdef.h>		/* attribute request codes */
1296 #ifdef NOTDEFINED /*** Not all versions of VMS compilers have these.	 ***/
1297 #include <fchdef.h>		/* file characteristics */
1298 #include <fatdef.h>		/* file attribute defs */
1299 #else		  /*** So we'll define what we need from them ourselves. ***/
1300 #define FCH$V_CONTIGB	0x005	/* pos of cont best try bit */
1301 #define FCH$M_CONTIGB	(1 << FCH$V_CONTIGB)	/* contig best try bit mask */
1302 /* VMS I/O User's Reference Manual: Part I (V5.x doc set) */
1303 struct fatdef {
1304     unsigned char fat$b_rtype, fat$b_rattrib;
1305     unsigned short fat$w_rsize;
1306     unsigned long fat$l_hiblk, fat$l_efblk;
1307     unsigned short fat$w_ffbyte;
1308     unsigned char fat$b_bktsize, fat$b_vfcsize;
1309     unsigned short fat$w_maxrec, fat$w_defext, fat$w_gbc;
1310     unsigned:16,:32,:16;	/* 6 bytes reserved, 2 bytes not used */
1311     unsigned short fat$w_versions;
1312 };
1313 #endif /* NOTDEFINED */
1314 
1315 /* arbitrary descriptor without type and class info */
1316 typedef struct dsc {
1317     unsigned short len, mbz;
1318     void *adr;
1319 } Desc;
1320 
1321 extern unsigned long sys$open(), sys$qiow(), sys$dassgn();
1322 
1323 #define syswork(sts)	((sts) & 1)
1324 #define sysfail(sts)	(!syswork(sts))
1325 
1326 /*
1327  * 25-Jul-1995 - Pat Rankin (rankin@eql.caltech.edu)
1328  *
1329  * Force a file to be marked as having fixed-length, 512 byte records
1330  * without implied carriage control, and with best_try_contiguous set.
1331  */
LYVMS_FixedLengthRecords(char * filename)1332 static unsigned long LYVMS_FixedLengthRecords(char *filename)
1333 {
1334     struct FAB fab;		/* RMS file access block */
1335     struct fibdef fib;		/* XQP file information block */
1336     struct fatdef recattr;	/* XQP file "record" attributes */
1337     struct atrdef attr_rqst_list[3];	/* XQP attribute request itemlist */
1338 
1339     Desc fib_dsc;
1340     unsigned short channel, iosb[4];
1341     unsigned long fchars, sts, tmp;
1342 
1343     /* initialize file access block */
1344     fab = cc$rms_fab;
1345     fab.fab$l_fna = filename;
1346     fab.fab$b_fns = (unsigned char) strlen(filename);
1347     fab.fab$l_fop = FAB$M_UFO;	/* user file open; no further RMS processing */
1348     fab.fab$b_fac = FAB$M_PUT;	/* need write access */
1349     fab.fab$b_shr = FAB$M_NIL;	/* exclusive access */
1350 
1351     sts = sys$open(&fab);	/* channel in stv; $dassgn to close */
1352     if (sts == RMS$_FLK) {
1353 	/* For MultiNet, at least, if the file was just written by a remote
1354 	   NFS client, the local NFS server might still have it open, and the
1355 	   failed access attempt will provoke it to be closed, so try again. */
1356 	sts = sys$open(&fab);
1357     }
1358     if (sysfail(sts))
1359 	return sts;
1360 
1361     /* RMS supplies a user-mode channel (see FAB$L_FOP FAB$V_UFO doc) */
1362     channel = (unsigned short) fab.fab$l_stv;
1363 
1364     /* set up ACP interface structures */
1365     /* file information block, passed by descriptor; it's okay to start with
1366        an empty FIB after RMS has accessed the file for us */
1367     fib_dsc.len = sizeof fib;
1368     fib_dsc.mbz = 0;
1369     fib_dsc.adr = &fib;
1370     memset((void *) &fib, 0, sizeof fib);
1371     /* attribute request list */
1372     attr_rqst_list[0].atr$w_size = sizeof recattr;
1373     attr_rqst_list[0].atr$w_type = ATR$C_RECATTR;
1374     *(void **) &attr_rqst_list[0].atr$l_addr = (void *) &recattr;
1375     attr_rqst_list[1].atr$w_size = sizeof fchars;
1376     attr_rqst_list[1].atr$w_type = ATR$C_UCHAR;
1377     *(void **) &attr_rqst_list[1].atr$l_addr = (void *) &fchars;
1378     attr_rqst_list[2].atr$w_size = attr_rqst_list[2].atr$w_type = 0;
1379     attr_rqst_list[2].atr$l_addr = 0;
1380     /* file "record" attributes */
1381     memset((void *) &recattr, 0, sizeof recattr);
1382     fchars = 0;			/* file characteristics */
1383 
1384     /* get current attributes */
1385     sts = sys$qiow(0, channel, IO$_ACCESS, iosb, (void (*)()) 0, 0,
1386 		   &fib_dsc, 0, 0, 0, attr_rqst_list, 0);
1387     if (syswork(sts))
1388 	sts = iosb[0];
1389 
1390     /* set desired attributes */
1391     if (syswork(sts)) {
1392 	recattr.fat$b_rtype = FAB$C_SEQ | FAB$C_FIX;	/* org=seq, rfm=fix */
1393 	recattr.fat$w_rsize = recattr.fat$w_maxrec = 512;	/* lrl=mrs=512 */
1394 	recattr.fat$b_rattrib = 0;	/* rat=none */
1395 	fchars |= FCH$M_CONTIGB;	/* contiguous-best-try */
1396 	sts = sys$qiow(0, channel, IO$_DEACCESS, iosb, (void (*)()) 0, 0,
1397 		       &fib_dsc, 0, 0, 0, attr_rqst_list, 0);
1398 	if (syswork(sts))
1399 	    sts = iosb[0];
1400     }
1401 
1402     /* all done */
1403     tmp = sys$dassgn(channel);
1404     if (syswork(sts))
1405 	sts = tmp;
1406     return sts;
1407 }
1408 #endif /* VMS && !USE_COMMAND_FILE */
1409