1 /*
2  * $LynxId: HTFormat.c,v 1.91 2019/01/02 23:25:10 tom Exp $
3  *
4  *		Manage different file formats			HTFormat.c
5  *		=============================
6  *
7  * Bugs:
8  *	Not reentrant.
9  *
10  *	Assumes the incoming stream is ASCII, rather than a local file
11  *	format, and so ALWAYS converts from ASCII on non-ASCII machines.
12  *	Therefore, non-ASCII machines can't read local files.
13  *
14  */
15 
16 #define HTSTREAM_INTERNAL 1
17 
18 #include <HTUtils.h>
19 
20 /* Implements:
21 */
22 #include <HTFormat.h>
23 
24 static float HTMaxSecs = 1e10;	/* No effective limit */
25 
26 #ifdef UNIX
27 #ifdef NeXT
28 #define PRESENT_POSTSCRIPT "open %s; /bin/rm -f %s\n"
29 #else
30 #define PRESENT_POSTSCRIPT "(ghostview %s ; /bin/rm -f %s)&\n"
31 			   /* Full pathname would be better! */
32 #endif /* NeXT */
33 #endif /* UNIX */
34 
35 #include <HTML.h>
36 #include <HTMLDTD.h>
37 #include <HText.h>
38 #include <HTAlert.h>
39 #include <HTList.h>
40 #include <HTInit.h>
41 #include <HTTCP.h>
42 #include <HTTP.h>
43 /*	Streams and structured streams which we use:
44 */
45 #include <HTFWriter.h>
46 #include <HTPlain.h>
47 #include <SGML.h>
48 #include <HTMLGen.h>
49 
50 #include <LYexit.h>
51 #include <LYUtils.h>
52 #include <GridText.h>
53 #include <LYGlobalDefs.h>
54 #include <LYLeaks.h>
55 
56 #ifdef DISP_PARTIAL
57 #include <LYMainLoop.h>
58 #endif
59 
60 BOOL HTOutputSource = NO;	/* Flag: shortcut parser to stdout */
61 
62 /* this version used by the NetToText stream */
63 struct _HTStream {
64     const HTStreamClass *isa;
65     BOOL had_cr;
66     HTStream *sink;
67 };
68 
69 /*	Presentation methods
70  *	--------------------
71  */
72 HTList *HTPresentations = NULL;
73 HTPresentation *default_presentation = NULL;
74 
75 /*
76  *	To free off the presentation list.
77  */
78 #ifdef LY_FIND_LEAKS
79 static void HTFreePresentations(void);
80 #endif
81 
82 /*	Define a presentation system command for a content-type
83  *	-------------------------------------------------------
84  */
HTSetPresentation(const char * representation,const char * command,const char * testcommand,double quality,double secs,double secs_per_byte,long int maxbytes,AcceptMedia media)85 void HTSetPresentation(const char *representation,
86 		       const char *command,
87 		       const char *testcommand,
88 		       double quality,
89 		       double secs,
90 		       double secs_per_byte,
91 		       long int maxbytes,
92 		       AcceptMedia media)
93 {
94     HTPresentation *pres = typecalloc(HTPresentation);
95 
96     if (pres == NULL)
97 	outofmem(__FILE__, "HTSetPresentation");
98 
99     assert(representation != NULL);
100 
101     CTRACE2(TRACE_CFG,
102 	    (tfp,
103 	     "HTSetPresentation rep=%s, command=%s, test=%s, qual=%f\n",
104 	     NonNull(representation),
105 	     NonNull(command),
106 	     NonNull(testcommand),
107 	     quality));
108 
109     pres->rep = HTAtom_for(representation);
110     pres->rep_out = WWW_PRESENT;	/* Fixed for now ... :-) */
111     pres->converter = HTSaveAndExecute;		/* Fixed for now ...     */
112     pres->quality = (float) quality;
113     pres->secs = (float) secs;
114     pres->secs_per_byte = (float) secs_per_byte;
115     pres->maxbytes = maxbytes;
116     pres->get_accept = 0;
117     pres->accept_opt = media;
118 
119     pres->command = NULL;
120     StrAllocCopy(pres->command, command);
121 
122     pres->testcommand = NULL;
123     StrAllocCopy(pres->testcommand, testcommand);
124 
125     /*
126      * Memory leak fixed.
127      * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe
128      */
129     if (!HTPresentations) {
130 	HTPresentations = HTList_new();
131 #ifdef LY_FIND_LEAKS
132 	atexit(HTFreePresentations);
133 #endif
134     }
135 
136     if (strcmp(representation, "*") == 0) {
137 	FREE(default_presentation);
138 	default_presentation = pres;
139     } else {
140 	HTList_addObject(HTPresentations, pres);
141     }
142 }
143 
144 /*	Define a built-in function for a content-type
145  *	---------------------------------------------
146  */
HTSetConversion(const char * representation_in,const char * representation_out,HTConverter * converter,double quality,double secs,double secs_per_byte,long int maxbytes,AcceptMedia media)147 void HTSetConversion(const char *representation_in,
148 		     const char *representation_out,
149 		     HTConverter *converter,
150 		     double quality,
151 		     double secs,
152 		     double secs_per_byte,
153 		     long int maxbytes,
154 		     AcceptMedia media)
155 {
156     HTPresentation *pres = typecalloc(HTPresentation);
157 
158     if (pres == NULL)
159 	outofmem(__FILE__, "HTSetConversion");
160 
161     CTRACE2(TRACE_CFG,
162 	    (tfp,
163 	     "HTSetConversion rep_in=%s, rep_out=%s, qual=%f\n",
164 	     NonNull(representation_in),
165 	     NonNull(representation_out),
166 	     quality));
167 
168     pres->rep = HTAtom_for(representation_in);
169     pres->rep_out = HTAtom_for(representation_out);
170     pres->converter = converter;
171     pres->command = NULL;
172     pres->testcommand = NULL;
173     pres->quality = (float) quality;
174     pres->secs = (float) secs;
175     pres->secs_per_byte = (float) secs_per_byte;
176     pres->maxbytes = maxbytes;
177     pres->get_accept = TRUE;
178     pres->accept_opt = media;
179 
180     /*
181      * Memory Leak fixed.
182      * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe
183      */
184     if (!HTPresentations) {
185 	HTPresentations = HTList_new();
186 #ifdef LY_FIND_LEAKS
187 	atexit(HTFreePresentations);
188 #endif
189     }
190 
191     HTList_addObject(HTPresentations, pres);
192 }
193 
194 #ifdef LY_FIND_LEAKS
195 /*
196  *	Purpose:	Free the presentation list.
197  *	Arguments:	void
198  *	Return Value:	void
199  *	Remarks/Portability/Dependencies/Restrictions:
200  *		Made to clean up Lynx's bad leakage.
201  *	Revision History:
202  *		05-28-94	created Lynx 2-3-1 Garrett Arch Blythe
203  */
HTFreePresentations(void)204 static void HTFreePresentations(void)
205 {
206     HTPresentation *pres = NULL;
207 
208     /*
209      * Loop through the list.
210      */
211     while (!HTList_isEmpty(HTPresentations)) {
212 	/*
213 	 * Free off each item.  May also need to free off it's items, but not
214 	 * sure as of yet.
215 	 */
216 	pres = (HTPresentation *) HTList_removeLastObject(HTPresentations);
217 	FREE(pres->command);
218 	FREE(pres->testcommand);
219 	FREE(pres);
220     }
221     /*
222      * Free the list itself.
223      */
224     HTList_delete(HTPresentations);
225     HTPresentations = NULL;
226 }
227 #endif /* LY_FIND_LEAKS */
228 
229 /*	File buffering
230  *	--------------
231  *
232  *	The input file is read using the macro which can read from
233  *	a socket or a file.
234  *	The input buffer size, if large will give greater efficiency and
235  *	release the server faster, and if small will save space on PCs etc.
236  */
237 #define INPUT_BUFFER_SIZE 4096	/* Tradeoff */
238 static char input_buffer[INPUT_BUFFER_SIZE];
239 static char *input_pointer;
240 static char *input_limit;
241 static int input_file_number;
242 
243 /*	Set up the buffering
244  *
245  *	These routines are public because they are in fact needed by
246  *	many parsers, and on PCs and Macs we should not duplicate
247  *	the static buffer area.
248  */
HTInitInput(int file_number)249 void HTInitInput(int file_number)
250 {
251     input_file_number = file_number;
252     input_pointer = input_limit = input_buffer;
253 }
254 
255 int interrupted_in_htgetcharacter = 0;
HTGetCharacter(void)256 int HTGetCharacter(void)
257 {
258     char ch;
259 
260     interrupted_in_htgetcharacter = 0;
261     do {
262 	if (input_pointer >= input_limit) {
263 	    int status = NETREAD(input_file_number,
264 				 input_buffer, INPUT_BUFFER_SIZE);
265 
266 	    if (status <= 0) {
267 		if (status == 0)
268 		    return EOF;
269 		if (status == HT_INTERRUPTED) {
270 		    CTRACE((tfp, "HTFormat: Interrupted in HTGetCharacter\n"));
271 		    interrupted_in_htgetcharacter = 1;
272 		    return EOF;
273 		}
274 		CTRACE((tfp, "HTFormat: File read error %d\n", status));
275 		return EOF;	/* -1 is returned by UCX
276 				   at end of HTTP link */
277 	    }
278 	    input_pointer = input_buffer;
279 	    input_limit = input_buffer + status;
280 	}
281 	ch = *input_pointer++;
282     } while (ch == (char) 13);	/* Ignore ASCII carriage return */
283 
284     return FROMASCII(UCH(ch));
285 }
286 
287 #ifdef USE_SSL
HTGetSSLCharacter(void * handle)288 int HTGetSSLCharacter(void *handle)
289 {
290     char ch;
291 
292     interrupted_in_htgetcharacter = 0;
293     if (!handle)
294 	return (char) EOF;
295     do {
296 	if (input_pointer >= input_limit) {
297 	    int status = SSL_read((SSL *) handle,
298 				  input_buffer, INPUT_BUFFER_SIZE);
299 
300 	    if (status <= 0) {
301 		if (status == 0)
302 		    return (char) EOF;
303 		if (status == HT_INTERRUPTED) {
304 		    CTRACE((tfp,
305 			    "HTFormat: Interrupted in HTGetSSLCharacter\n"));
306 		    interrupted_in_htgetcharacter = 1;
307 		    return (char) EOF;
308 		}
309 		CTRACE((tfp, "HTFormat: SSL_read error %d\n", status));
310 		return (char) EOF;	/* -1 is returned by UCX
311 					   at end of HTTP link */
312 	    }
313 	    input_pointer = input_buffer;
314 	    input_limit = input_buffer + status;
315 	}
316 	ch = *input_pointer++;
317     } while (ch == (char) 13);	/* Ignore ASCII carriage return */
318 
319     return FROMASCII(ch);
320 }
321 #endif /* USE_SSL */
322 
323 /* Match maintype to any MIME type starting with maintype, for example:
324  * image/gif should match image
325  */
half_match(char * trial_type,char * target)326 static int half_match(char *trial_type, char *target)
327 {
328     char *cp = StrChr(trial_type, '/');
329 
330     /* if no '/' or no '*' */
331     if (!cp || *(cp + 1) != '*')
332 	return 0;
333 
334     CTRACE((tfp, "HTFormat: comparing %s and %s for half match\n",
335 	    trial_type, target));
336 
337     /* main type matches */
338     if (!StrNCmp(trial_type, target, ((cp - trial_type) - 1)))
339 	return 1;
340 
341     return 0;
342 }
343 
344 /*
345  * Evaluate a deferred mailcap test command, i.e.,. one that substitutes the
346  * document's charset or other values in %{name} format.
347  */
failsMailcap(HTPresentation * pres,HTParentAnchor * anchor)348 static BOOL failsMailcap(HTPresentation *pres, HTParentAnchor *anchor)
349 {
350     if (pres->testcommand != NULL &&
351 	anchor != NULL &&
352 	anchor->content_type_params != NULL) {
353 	if (LYTestMailcapCommand(pres->testcommand,
354 				 anchor->content_type_params) != 0)
355 	    return TRUE;
356     }
357     return FALSE;
358 }
359 
360 #define WWW_WILDCARD_REP_OUT HTAtom_for("*")
361 
362 /*		Look up a presentation
363  *		----------------------
364  *
365  *	If fill_in is NULL, only look for an exact match.
366  *	If a wildcard match is made, *fill_in is used to store
367  *	a possibly modified presentation, and a pointer to it is
368  *	returned.  For an exact match, a pointer to the presentation
369  *	in the HTPresentations list is returned.  Returns NULL if
370  *	nothing found. - kw
371  *
372  */
HTFindPresentation(HTFormat rep_in,HTFormat rep_out,HTPresentation * fill_in,HTParentAnchor * anchor)373 static HTPresentation *HTFindPresentation(HTFormat rep_in,
374 					  HTFormat rep_out,
375 					  HTPresentation *fill_in,
376 					  HTParentAnchor *anchor)
377 {
378     HTAtom *wildcard = NULL;	/* = HTAtom_for("*"); lookup when needed - kw */
379     int n;
380     int i;
381     HTPresentation *pres;
382     HTPresentation *match;
383     HTPresentation *strong_wildcard_match = 0;
384     HTPresentation *weak_wildcard_match = 0;
385     HTPresentation *last_default_match = 0;
386     HTPresentation *strong_subtype_wildcard_match = 0;
387 
388     CTRACE((tfp, "HTFormat: Looking up presentation for %s to %s\n",
389 	    HTAtom_name(rep_in), HTAtom_name(rep_out)));
390 
391     n = HTList_count(HTPresentations);
392     for (i = 0; i < n; i++) {
393 	pres = (HTPresentation *) HTList_objectAt(HTPresentations, i);
394 	if (pres->rep == rep_in) {
395 	    if (pres->rep_out == rep_out) {
396 		if (failsMailcap(pres, anchor))
397 		    continue;
398 		CTRACE((tfp, "FindPresentation: found exact match: %s -> %s\n",
399 			HTAtom_name(pres->rep),
400 			HTAtom_name(pres->rep_out)));
401 		return pres;
402 
403 	    } else if (!fill_in) {
404 		continue;
405 	    } else {
406 		if (!wildcard)
407 		    wildcard = WWW_WILDCARD_REP_OUT;
408 		if (pres->rep_out == wildcard) {
409 		    if (failsMailcap(pres, anchor))
410 			continue;
411 		    if (!strong_wildcard_match)
412 			strong_wildcard_match = pres;
413 		    /* otherwise use the first one */
414 		    CTRACE((tfp,
415 			    "StreamStack: found strong wildcard match: %s -> %s\n",
416 			    HTAtom_name(pres->rep),
417 			    HTAtom_name(pres->rep_out)));
418 		}
419 	    }
420 
421 	} else if (!fill_in) {
422 	    continue;
423 
424 	} else if (half_match(HTAtom_name(pres->rep),
425 			      HTAtom_name(rep_in))) {
426 	    if (pres->rep_out == rep_out) {
427 		if (failsMailcap(pres, anchor))
428 		    continue;
429 		if (!strong_subtype_wildcard_match)
430 		    strong_subtype_wildcard_match = pres;
431 		/* otherwise use the first one */
432 		CTRACE((tfp,
433 			"StreamStack: found strong subtype wildcard match: %s -> %s\n",
434 			HTAtom_name(pres->rep),
435 			HTAtom_name(pres->rep_out)));
436 	    }
437 	}
438 
439 	if (pres->rep == WWW_SOURCE) {
440 	    if (pres->rep_out == rep_out) {
441 		if (failsMailcap(pres, anchor))
442 		    continue;
443 		if (!weak_wildcard_match)
444 		    weak_wildcard_match = pres;
445 		/* otherwise use the first one */
446 		CTRACE((tfp,
447 			"StreamStack: found weak wildcard match: %s\n",
448 			HTAtom_name(pres->rep_out)));
449 
450 	    } else if (!last_default_match) {
451 		if (!wildcard)
452 		    wildcard = WWW_WILDCARD_REP_OUT;
453 		if (pres->rep_out == wildcard) {
454 		    if (failsMailcap(pres, anchor))
455 			continue;
456 		    last_default_match = pres;
457 		    /* otherwise use the first one */
458 		}
459 	    }
460 	}
461     }
462 
463     match = (strong_subtype_wildcard_match
464 	     ? strong_subtype_wildcard_match
465 	     : (strong_wildcard_match
466 		? strong_wildcard_match
467 		: (weak_wildcard_match
468 		   ? weak_wildcard_match
469 		   : last_default_match)));
470 
471     if (match) {
472 	*fill_in = *match;	/* Specific instance */
473 	fill_in->rep = rep_in;	/* yuk */
474 	fill_in->rep_out = rep_out;	/* yuk */
475 	return fill_in;
476     }
477 
478     return NULL;
479 }
480 
481 /*		Create a filter stack
482  *		---------------------
483  *
484  *	If a wildcard match is made, a temporary HTPresentation
485  *	structure is made to hold the destination format while the
486  *	new stack is generated. This is just to pass the out format to
487  *	MIME so far.  Storing the format of a stream in the stream might
488  *	be a lot neater.
489  *
490  */
HTStreamStack(HTFormat rep_in,HTFormat rep_out,HTStream * sink,HTParentAnchor * anchor)491 HTStream *HTStreamStack(HTFormat rep_in,
492 			HTFormat rep_out,
493 			HTStream *sink,
494 			HTParentAnchor *anchor)
495 {
496     HTPresentation temp;
497     HTPresentation *match;
498     HTStream *result;
499 
500     CTRACE((tfp, "StreamStack: Constructing stream stack for %s to %s (%s)\n",
501 	    HTAtom_name(rep_in),
502 	    HTAtom_name(rep_out),
503 	    NONNULL(anchor->content_type_params)));
504 
505     if (rep_out == rep_in) {
506 	result = sink;
507 
508     } else if ((match = HTFindPresentation(rep_in, rep_out, &temp, anchor))) {
509 	if (match == &temp) {
510 	    CTRACE((tfp, "StreamStack: Using %s\n", HTAtom_name(temp.rep_out)));
511 	} else {
512 	    CTRACE((tfp, "StreamStack: found exact match: %s -> %s\n",
513 		    HTAtom_name(match->rep),
514 		    HTAtom_name(match->rep_out)));
515 	}
516 	result = (*match->converter) (match, anchor, sink);
517     } else {
518 	result = NULL;
519     }
520     if (TRACE) {
521 	if (result && result->isa && result->isa->name) {
522 	    CTRACE((tfp, "StreamStack: Returning \"%s\"\n", result->isa->name));
523 	} else if (result) {
524 	    CTRACE((tfp, "StreamStack: Returning *unknown* stream!\n"));
525 	} else {
526 	    CTRACE((tfp, "StreamStack: Returning NULL!\n"));
527 	    CTRACE_FLUSH(tfp);	/* a crash may be imminent... - kw */
528 	}
529     }
530     return result;
531 }
532 
533 /*		Put a presentation near start of list
534  *		-------------------------------------
535  *
536  *	Look up a presentation (exact match only) and, if found, reorder
537  *	it to the start of the HTPresentations list. - kw
538  */
HTReorderPresentation(HTFormat rep_in,HTFormat rep_out)539 void HTReorderPresentation(HTFormat rep_in,
540 			   HTFormat rep_out)
541 {
542     HTPresentation *match;
543 
544     if ((match = HTFindPresentation(rep_in, rep_out, NULL, NULL))) {
545 	HTList_removeObject(HTPresentations, match);
546 	HTList_addObject(HTPresentations, match);
547     }
548 }
549 
550 /*
551  * Setup 'get_accept' flag to denote presentations that are not redundant,
552  * and will be listed in "Accept:" header.
553  */
HTFilterPresentations(void)554 void HTFilterPresentations(void)
555 {
556     int i, j;
557     int n = HTList_count(HTPresentations);
558     HTPresentation *p, *q;
559     BOOL matched;
560     char *s, *t;
561 
562     CTRACE((tfp, "HTFilterPresentations (AcceptMedia %#x)\n", LYAcceptMedia));
563     for (i = 0; i < n; i++) {
564 	p = (HTPresentation *) HTList_objectAt(HTPresentations, i);
565 	s = HTAtom_name(p->rep);
566 
567 	p->get_accept = FALSE;
568 	if ((LYAcceptMedia & p->accept_opt) != 0
569 	    && p->rep_out == WWW_PRESENT
570 	    && p->rep != WWW_SOURCE
571 	    && strcasecomp(s, "www/mime")
572 	    && strcasecomp(s, "www/compressed")
573 	    && p->quality <= 1.0 && p->quality >= 0.0) {
574 	    matched = TRUE;
575 	    for (j = 0; j < i; j++) {
576 		q = (HTPresentation *) HTList_objectAt(HTPresentations, j);
577 		t = HTAtom_name(q->rep);
578 
579 		if (!strcasecomp(s, t)) {
580 		    matched = FALSE;
581 		    CTRACE((tfp, "  match %s %s\n", s, t));
582 		    break;
583 		}
584 	    }
585 	    p->get_accept = matched;
586 	}
587     }
588 }
589 
590 /*		Find the cost of a filter stack
591  *		-------------------------------
592  *
593  *	Must return the cost of the same stack which StreamStack would set up.
594  *
595  * On entry,
596  *	length	The size of the data to be converted
597  */
HTStackValue(HTFormat rep_in,HTFormat rep_out,double initial_value,long int length)598 float HTStackValue(HTFormat rep_in,
599 		   HTFormat rep_out,
600 		   double initial_value,
601 		   long int length)
602 {
603     HTAtom *wildcard = WWW_WILDCARD_REP_OUT;
604 
605     CTRACE((tfp, "HTFormat: Evaluating stream stack for %s worth %.3f to %s\n",
606 	    HTAtom_name(rep_in), initial_value, HTAtom_name(rep_out)));
607 
608     if (rep_out == WWW_SOURCE || rep_out == rep_in)
609 	return 0.0;
610 
611     {
612 	int n = HTList_count(HTPresentations);
613 	int i;
614 	HTPresentation *pres;
615 
616 	for (i = 0; i < n; i++) {
617 	    pres = (HTPresentation *) HTList_objectAt(HTPresentations, i);
618 	    if (pres->rep == rep_in &&
619 		(pres->rep_out == rep_out || pres->rep_out == wildcard)) {
620 		float value = (float) (initial_value * pres->quality);
621 
622 		if (HTMaxSecs > 0.0)
623 		    value = (value
624 			     - ((float) length * pres->secs_per_byte
625 				+ pres->secs)
626 			     / HTMaxSecs);
627 		return value;
628 	    }
629 	}
630     }
631 
632     return (float) -1e30;	/* Really bad */
633 
634 }
635 
636 /*	Display the page while transfer in progress
637  *	-------------------------------------------
638  *
639  *   Repaint the page only when necessary.
640  *   This is a traverse call for HText_pageDisplay() - it works!.
641  *
642  */
HTDisplayPartial(void)643 void HTDisplayPartial(void)
644 {
645 #ifdef DISP_PARTIAL
646     if (display_partial) {
647 	/*
648 	 * HText_getNumOfLines() = "current" number of complete lines received
649 	 * NumOfLines_partial = number of lines at the moment of last repaint.
650 	 * (we update NumOfLines_partial only when we repaint the display.)
651 	 *
652 	 * display_partial could only be enabled in HText_new() so a new
653 	 * HTMainText object available - all HText_ functions use it, lines
654 	 * counter HText_getNumOfLines() in particular.
655 	 *
656 	 * Otherwise HTMainText holds info from the previous document and we
657 	 * may repaint it instead of the new one:  prev doc scrolled to the
658 	 * first line (=Newline_partial) is not good looking :-) 23 Aug 1998
659 	 * Leonid Pauzner
660 	 *
661 	 * So repaint the page only when necessary:
662 	 */
663 	int Newline_partial = LYGetNewline();
664 
665 	if (((Newline_partial + display_lines) - 1 > NumOfLines_partial)
666 	/* current page not complete... */
667 	    && (partial_threshold > 0 ?
668 		((Newline_partial + partial_threshold) - 1 <=
669 		 HText_getNumOfLines()) :
670 		((Newline_partial + display_lines) - 1 <= HText_getNumOfLines()))
671 	/*
672 	 * Originally we rendered by increments of 2 lines,
673 	 * but that got annoying on slow network connections.
674 	 * Then we switched to full-pages.  Now it's configurable.
675 	 * If partial_threshold <= 0, then it's a full page
676 	 */
677 	    ) {
678 	    if (LYMainLoop_pageDisplay(Newline_partial))
679 		NumOfLines_partial = HText_getNumOfLines();
680 	}
681     }
682 #else /* nothing */
683 #endif /* DISP_PARTIAL */
684 }
685 
686 /* Put this as early as possible, OK just after HTDisplayPartial() */
HTFinishDisplayPartial(void)687 void HTFinishDisplayPartial(void)
688 {
689 #ifdef DISP_PARTIAL
690     /*
691      * End of incremental rendering stage here.
692      */
693     display_partial = FALSE;
694 #endif /* DISP_PARTIAL */
695 }
696 
697 /*	Push data from a socket down a stream
698  *	-------------------------------------
699  *
700  *   This routine is responsible for creating and PRESENTING any
701  *   graphic (or other) objects described by the file.
702  *
703  *   The file number given is assumed to be a TELNET stream, i.e., containing
704  *   CRLF at the end of lines which need to be stripped to LF for unix
705  *   when the format is textual.
706  *
707  *  State of socket and target stream on entry:
708  *			socket (file_number) assumed open,
709  *			target (sink) assumed valid.
710  *
711  *  Return values:
712  *	HT_INTERRUPTED  Interruption or error after some data received.
713  *	-2		Unexpected disconnect before any data received.
714  *	-1		Interruption or error before any data received, or
715  *			(UNIX) other read error before any data received, or
716  *			download cancelled.
717  *	HT_LOADED	Normal close of socket (end of file indication
718  *			received), or
719  *			unexpected disconnect after some data received, or
720  *			other read error after some data received, or
721  *			(not UNIX) other read error before any data received.
722  *
723  *  State of socket and target stream on return depends on return value:
724  *	HT_INTERRUPTED	socket still open, target aborted.
725  *	-2		socket still open, target stream still valid.
726  *	-1		socket still open, target aborted.
727  *	otherwise	socket closed,	target stream still valid.
728  */
HTCopy(HTParentAnchor * anchor,int file_number,void * handle GCC_UNUSED,HTStream * sink)729 int HTCopy(HTParentAnchor *anchor,
730 	   int file_number,
731 	   void *handle GCC_UNUSED,
732 	   HTStream *sink)
733 {
734     HTStreamClass targetClass;
735     BOOL suppress_readprogress = NO;
736     off_t limit = anchor ? anchor->content_length : 0;
737     off_t bytes = 0;
738     off_t header_length = 0;
739     int rv = 0;
740 
741     /*  Push the data down the stream
742      */
743     targetClass = *(sink->isa);	/* Copy pointers to procedures */
744 
745     /*
746      * Push binary from socket down sink
747      *
748      * This operation could be put into a main event loop
749      */
750     HTReadProgress(bytes, (off_t) 0);
751     for (;;) {
752 	int status;
753 
754 	if (LYCancelDownload) {
755 	    LYCancelDownload = FALSE;
756 	    (*targetClass._abort) (sink, NULL);
757 	    rv = -1;
758 	    goto finished;
759 	}
760 
761 	if (HTCheckForInterrupt()) {
762 	    _HTProgress(TRANSFER_INTERRUPTED);
763 	    (*targetClass._abort) (sink, NULL);
764 	    if (bytes)
765 		rv = HT_INTERRUPTED;
766 	    else
767 		rv = -1;
768 	    goto finished;
769 	}
770 #ifdef USE_SSL
771 	if (handle)
772 	    status = SSL_read((SSL *) handle, input_buffer, INPUT_BUFFER_SIZE);
773 	else
774 	    status = NETREAD(file_number, input_buffer, INPUT_BUFFER_SIZE);
775 #else
776 	status = NETREAD(file_number, input_buffer, INPUT_BUFFER_SIZE);
777 #endif /* USE_SSL */
778 	if (status <= 0) {
779 	    if (status == 0) {
780 		break;
781 	    } else if (status == HT_INTERRUPTED) {
782 		_HTProgress(TRANSFER_INTERRUPTED);
783 		(*targetClass._abort) (sink, NULL);
784 		if (bytes)
785 		    rv = HT_INTERRUPTED;
786 		else
787 		    rv = -1;
788 		goto finished;
789 	    } else if (SOCKET_ERRNO == ENOTCONN ||
790 #ifdef _WINDOWS			/* 1997/11/10 (Mon) 16:57:18 */
791 		       SOCKET_ERRNO == ETIMEDOUT ||
792 #endif
793 		       SOCKET_ERRNO == ECONNRESET ||
794 		       SOCKET_ERRNO == EPIPE) {
795 		/*
796 		 * Arrrrgh, HTTP 0/1 compatibility problem, maybe.
797 		 */
798 		if (bytes <= 0) {
799 		    /*
800 		     * Don't have any data, so let the calling function decide
801 		     * what to do about it.  - FM
802 		     */
803 		    rv = -2;
804 		    goto finished;
805 		} else {
806 #ifdef UNIX
807 		    /*
808 		     * Treat what we've received already as the complete
809 		     * transmission, but not without giving the user an alert.
810 		     * I don't know about all the different TCP stacks for VMS
811 		     * etc., so this is currently only for UNIX.  - kw
812 		     */
813 		    HTInetStatus("NETREAD");
814 		    HTAlert("Unexpected server disconnect.");
815 		    CTRACE((tfp,
816 			    "HTCopy: Unexpected server disconnect. Treating as completed.\n"));
817 #else /* !UNIX */
818 		    /*
819 		     * Treat what we've gotten already as the complete
820 		     * transmission.  - FM
821 		     */
822 		    CTRACE((tfp,
823 			    "HTCopy: Unexpected server disconnect.  Treating as completed.\n"));
824 		    status = 0;
825 #endif /* UNIX */
826 		}
827 #ifdef UNIX
828 	    } else {		/* status < 0 and other errno */
829 		/*
830 		 * Treat what we've received already as the complete
831 		 * transmission, but not without giving the user an alert.  I
832 		 * don't know about all the different TCP stacks for VMS etc.,
833 		 * so this is currently only for UNIX.  - kw
834 		 */
835 		HTInetStatus("NETREAD");
836 		HTAlert("Unexpected read error.");
837 		if (bytes) {
838 		    (void) NETCLOSE(file_number);
839 		    rv = HT_LOADED;
840 		} else {
841 		    (*targetClass._abort) (sink, NULL);
842 		    rv = -1;
843 		}
844 		goto finished;
845 #endif
846 	    }
847 	    break;
848 	}
849 
850 	/*
851 	 * Suppress ReadProgress messages when collecting a redirection
852 	 * message, at least initially (unless/until anchor->content_type gets
853 	 * changed, probably by the MIME message parser).  That way messages
854 	 * put up by the HTTP module or elsewhere can linger in the statusline
855 	 * for a while.  - kw
856 	 */
857 	suppress_readprogress = (BOOL) (anchor && anchor->content_type &&
858 					!strcmp(anchor->content_type,
859 						"message/x-http-redirection"));
860 #ifdef NOT_ASCII
861 	{
862 	    char *p;
863 
864 	    for (p = input_buffer; p < input_buffer + status; p++) {
865 		*p = FROMASCII(*p);
866 	    }
867 	}
868 #endif /* NOT_ASCII */
869 
870 	header_length = anchor != 0 ? anchor->header_length : 0;
871 
872 	(*targetClass.put_block) (sink, input_buffer, status);
873 	if (anchor != 0 && anchor->inHEAD) {
874 	    if (!suppress_readprogress) {
875 		statusline(gettext("Reading headers..."));
876 	    }
877 	    CTRACE((tfp, "HTCopy read %" PRI_off_t " header bytes\n",
878 		    CAST_off_t (anchor->header_length)));
879 	} else {
880 	    /*
881 	     * If header-length is increased at this point, that is due to
882 	     * HTMIME, which detects the end of the server headers.  There
883 	     * may be additional (non-header) data in that block.
884 	     */
885 	    if (anchor != 0 && (anchor->header_length > header_length)) {
886 		int header = (int) (anchor->header_length - header_length);
887 
888 		CTRACE((tfp, "HTCopy read %" PRI_off_t " header bytes "
889 			"(%d extra vs %d total)\n",
890 			CAST_off_t (anchor->header_length),
891 			header, status));
892 		if (status > header) {
893 		    bytes += (status - header);
894 		}
895 	    } else {
896 		bytes += status;
897 	    }
898 	    if (!suppress_readprogress) {
899 		HTReadProgress(bytes, limit);
900 	    }
901 	    HTDisplayPartial();
902 	}
903 
904 	/* a few buggy implementations do not close the connection properly
905 	 * and will hang if we try to read past the declared content-length.
906 	 */
907 	if (limit > 0 && bytes >= limit)
908 	    break;
909     }				/* next bufferload */
910     if (anchor != 0) {
911 	CTRACE((tfp, "HTCopy copied %"
912 		PRI_off_t " actual, %"
913 		PRI_off_t " limit\n", CAST_off_t (bytes), CAST_off_t (limit)));
914 	anchor->actual_length = bytes;
915     }
916 
917     _HTProgress(TRANSFER_COMPLETE);
918     (void) NETCLOSE(file_number);
919     rv = HT_LOADED;
920 
921   finished:
922     HTFinishDisplayPartial();
923     return (rv);
924 }
925 
926 /*	Push data from a file pointer down a stream
927  *	-------------------------------------
928  *
929  *   This routine is responsible for creating and PRESENTING any
930  *   graphic (or other) objects described by the file.
931  *
932  *
933  *  State of file and target stream on entry:
934  *			FILE* (fp) assumed open,
935  *			target (sink) assumed valid.
936  *
937  *  Return values:
938  *	HT_INTERRUPTED  Interruption after some data read.
939  *	HT_PARTIAL_CONTENT	Error after some data read.
940  *	-1		Error before any data read.
941  *	HT_LOADED	Normal end of file indication on reading.
942  *
943  *  State of file and target stream on return:
944  *	always		fp still open, target stream still valid.
945  */
HTFileCopy(FILE * fp,HTStream * sink)946 int HTFileCopy(FILE *fp, HTStream *sink)
947 {
948     HTStreamClass targetClass;
949     int status;
950     off_t bytes;
951     int rv = HT_OK;
952 
953     /*  Push the data down the stream
954      */
955     targetClass = *(sink->isa);	/* Copy pointers to procedures */
956 
957     /*  Push binary from socket down sink
958      */
959     HTReadProgress(bytes = 0, (off_t) 0);
960     for (;;) {
961 	status = (int) fread(input_buffer,
962 			     (size_t) 1,
963 			     (size_t) INPUT_BUFFER_SIZE, fp);
964 	if (status == 0) {	/* EOF or error */
965 	    if (ferror(fp) == 0) {
966 		rv = HT_LOADED;
967 		break;
968 	    }
969 	    CTRACE((tfp, "HTFormat: Read error, read returns %d\n",
970 		    ferror(fp)));
971 	    if (bytes) {
972 		rv = HT_PARTIAL_CONTENT;
973 	    } else {
974 		rv = -1;
975 	    }
976 	    break;
977 	}
978 
979 	(*targetClass.put_block) (sink, input_buffer, status);
980 	bytes += status;
981 	HTReadProgress(bytes, (off_t) 0);
982 	/* Suppress last screen update in partial mode - a regular update under
983 	 * control of mainloop() should follow anyway.  - kw
984 	 */
985 #ifdef DISP_PARTIAL
986 	if (display_partial && bytes != HTMainAnchor->content_length)
987 	    HTDisplayPartial();
988 #endif
989 
990 	if (HTCheckForInterrupt()) {
991 	    _HTProgress(TRANSFER_INTERRUPTED);
992 	    if (bytes) {
993 		rv = HT_INTERRUPTED;
994 	    } else {
995 		rv = -1;
996 	    }
997 	    break;
998 	}
999     }				/* next bufferload */
1000 
1001     HTFinishDisplayPartial();
1002     return rv;
1003 }
1004 
1005 #ifdef USE_SOURCE_CACHE
1006 /*	Push data from an HTChunk down a stream
1007  *	---------------------------------------
1008  *
1009  *   This routine is responsible for creating and PRESENTING any
1010  *   graphic (or other) objects described by the file.
1011  *
1012  *  State of memory and target stream on entry:
1013  *			HTChunk* (chunk) and target (sink) assumed valid.
1014  *
1015  *  Return values:
1016  *	HT_LOADED	All data sent.
1017  *	HT_INTERRUPTED  Interruption after some data read.
1018  *
1019  *  State of memory and target stream on return:
1020  *	always		chunk unchanged, target stream still valid.
1021  */
HTMemCopy(HTChunk * chunk,HTStream * sink)1022 int HTMemCopy(HTChunk *chunk, HTStream *sink)
1023 {
1024     HTStreamClass targetClass;
1025     off_t bytes;
1026     int rv = HT_OK;
1027 
1028     targetClass = *(sink->isa);
1029     HTReadProgress(bytes = 0, (off_t) 0);
1030     for (; chunk != NULL; chunk = chunk->next) {
1031 
1032 	/* Push the data down the stream a piece at a time, in case we're
1033 	 * running a large document on a slow machine.
1034 	 */
1035 	(*targetClass.put_block) (sink, chunk->data, chunk->size);
1036 	bytes += chunk->size;
1037 
1038 	HTReadProgress(bytes, (off_t) 0);
1039 	HTDisplayPartial();
1040 
1041 	if (HTCheckForInterrupt()) {
1042 	    _HTProgress(TRANSFER_INTERRUPTED);
1043 	    if (bytes) {
1044 		rv = HT_INTERRUPTED;
1045 	    } else {
1046 		rv = -1;
1047 	    }
1048 	    break;
1049 	}
1050     }
1051 
1052     HTFinishDisplayPartial();
1053     return rv;
1054 }
1055 #endif
1056 
1057 #ifdef USE_ZLIB
1058 /*	Push data from a gzip file pointer down a stream
1059  *	-------------------------------------
1060  *
1061  *   This routine is responsible for creating and PRESENTING any
1062  *   graphic (or other) objects described by the file.
1063  *
1064  *
1065  *  State of file and target stream on entry:
1066  *		      gzFile (gzfp) assumed open (should have gzipped content),
1067  *		      target (sink) assumed valid.
1068  *
1069  *  Return values:
1070  *	HT_INTERRUPTED  Interruption after some data read.
1071  *	HT_PARTIAL_CONTENT	Error after some data read.
1072  *	-1		Error before any data read.
1073  *	HT_LOADED	Normal end of file indication on reading.
1074  *
1075  *  State of file and target stream on return:
1076  *	always		gzfp still open, target stream still valid.
1077  */
HTGzFileCopy(gzFile gzfp,HTStream * sink)1078 static int HTGzFileCopy(gzFile gzfp, HTStream *sink)
1079 {
1080     HTStreamClass targetClass;
1081     int status;
1082     off_t bytes;
1083     int gzerrnum;
1084     int rv = HT_OK;
1085 
1086     /*  Push the data down the stream
1087      */
1088     targetClass = *(sink->isa);	/* Copy pointers to procedures */
1089 
1090     /*  read and inflate gzip'd file, and push binary down sink
1091      */
1092     HTReadProgress(bytes = 0, (off_t) 0);
1093     for (;;) {
1094 	status = gzread(gzfp, input_buffer, INPUT_BUFFER_SIZE);
1095 	if (status <= 0) {	/* EOF or error */
1096 	    if (status == 0) {
1097 		rv = HT_LOADED;
1098 		break;
1099 	    }
1100 	    CTRACE((tfp, "HTGzFileCopy: Read error, gzread returns %d\n",
1101 		    status));
1102 	    CTRACE((tfp, "gzerror   : %s\n",
1103 		    gzerror(gzfp, &gzerrnum)));
1104 	    if (TRACE) {
1105 		if (gzerrnum == Z_ERRNO)
1106 		    perror("gzerror   ");
1107 	    }
1108 	    if (bytes) {
1109 		rv = HT_PARTIAL_CONTENT;
1110 	    } else {
1111 		rv = -1;
1112 	    }
1113 	    break;
1114 	}
1115 
1116 	(*targetClass.put_block) (sink, input_buffer, status);
1117 	bytes += status;
1118 	HTReadProgress(bytes, (off_t) -1);
1119 	HTDisplayPartial();
1120 
1121 	if (HTCheckForInterrupt()) {
1122 	    _HTProgress(TRANSFER_INTERRUPTED);
1123 	    rv = HT_INTERRUPTED;
1124 	    break;
1125 	}
1126     }				/* next bufferload */
1127 
1128     HTFinishDisplayPartial();
1129     return rv;
1130 }
1131 
1132 #ifndef HAVE_ZERROR
1133 #define zError(s) LynxZError(s)
zError(int status)1134 static const char *zError(int status)
1135 {
1136     static char result[80];
1137 
1138     sprintf(result, "zlib error %d", status);
1139     return result;
1140 }
1141 #endif
1142 
1143 /*	Push data from a deflate file pointer down a stream
1144  *	-------------------------------------
1145  *
1146  *  This routine is responsible for creating and PRESENTING any
1147  *  graphic (or other) objects described by the file.  The code is
1148  *  loosely based on the inflate.c file from w3m.
1149  *
1150  *
1151  *  State of file and target stream on entry:
1152  *		      FILE (zzfp) assumed open (should have deflated content),
1153  *		      target (sink) assumed valid.
1154  *
1155  *  Return values:
1156  *	HT_INTERRUPTED  Interruption after some data read.
1157  *	HT_PARTIAL_CONTENT	Error after some data read.
1158  *	-1		Error before any data read.
1159  *	HT_LOADED	Normal end of file indication on reading.
1160  *
1161  *  State of file and target stream on return:
1162  *	always		zzfp still open, target stream still valid.
1163  */
HTZzFileCopy(FILE * zzfp,HTStream * sink)1164 static int HTZzFileCopy(FILE *zzfp, HTStream *sink)
1165 {
1166     static char dummy_head[1 + 1] =
1167     {
1168 	0x8 + 0x7 * 0x10,
1169 	(((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF,
1170     };
1171 
1172     z_stream s;
1173     HTStreamClass targetClass;
1174     off_t bytes;
1175     int rv = HT_OK;
1176     char output_buffer[INPUT_BUFFER_SIZE];
1177     int status;
1178     int flush;
1179     int retry = 0;
1180     int len = 0;
1181 
1182     /*  Push the data down the stream
1183      */
1184     targetClass = *(sink->isa);	/* Copy pointers to procedures */
1185 
1186     s.zalloc = Z_NULL;
1187     s.zfree = Z_NULL;
1188     s.opaque = Z_NULL;
1189     status = inflateInit(&s);
1190     if (status != Z_OK) {
1191 	CTRACE((tfp, "HTZzFileCopy inflateInit() %s\n", zError(status)));
1192 	exit_immediately(EXIT_FAILURE);
1193     }
1194     s.avail_in = 0;
1195     s.next_out = (Bytef *) output_buffer;
1196     s.avail_out = sizeof(output_buffer);
1197     flush = Z_NO_FLUSH;
1198 
1199     /*  read and inflate deflate'd file, and push binary down sink
1200      */
1201     HTReadProgress(bytes = 0, (off_t) 0);
1202     for (;;) {
1203 	if (s.avail_in == 0) {
1204 	    s.next_in = (Bytef *) input_buffer;
1205 	    s.avail_in = (uInt) fread(input_buffer,
1206 				      (size_t) 1,
1207 				      (size_t) INPUT_BUFFER_SIZE, zzfp);
1208 	    len = (int) s.avail_in;
1209 	}
1210 	status = inflate(&s, flush);
1211 	if (status == Z_STREAM_END || status == Z_BUF_ERROR) {
1212 	    len = (int) sizeof(output_buffer) - (int) s.avail_out;
1213 	    if (len > 0) {
1214 		(*targetClass.put_block) (sink, output_buffer, len);
1215 		bytes += len;
1216 		HTReadProgress(bytes, (off_t) -1);
1217 		HTDisplayPartial();
1218 	    }
1219 	    rv = HT_LOADED;
1220 	    break;
1221 	} else if (status == Z_DATA_ERROR && !retry++) {
1222 	    status = inflateReset(&s);
1223 	    if (status != Z_OK) {
1224 		CTRACE((tfp, "HTZzFileCopy inflateReset() %s\n", zError(status)));
1225 		rv = -1;
1226 		break;
1227 	    }
1228 	    s.next_in = (Bytef *) dummy_head;
1229 	    s.avail_in = sizeof(dummy_head);
1230 	    (void) inflate(&s, flush);
1231 	    s.next_in = (Bytef *) input_buffer;
1232 	    s.avail_in = (unsigned) len;
1233 	    continue;
1234 	} else if (status != Z_OK) {
1235 	    CTRACE((tfp, "HTZzFileCopy inflate() %s\n", zError(status)));
1236 	    rv = bytes ? HT_PARTIAL_CONTENT : -1;
1237 	    break;
1238 	} else if (s.avail_out == 0) {
1239 	    len = sizeof(output_buffer);
1240 	    s.next_out = (Bytef *) output_buffer;
1241 	    s.avail_out = sizeof(output_buffer);
1242 
1243 	    (*targetClass.put_block) (sink, output_buffer, len);
1244 	    bytes += len;
1245 	    HTReadProgress(bytes, (off_t) -1);
1246 	    HTDisplayPartial();
1247 
1248 	    if (HTCheckForInterrupt()) {
1249 		_HTProgress(TRANSFER_INTERRUPTED);
1250 		rv = bytes ? HT_INTERRUPTED : -1;
1251 		break;
1252 	    }
1253 	}
1254 	retry = 1;
1255     }				/* next bufferload */
1256 
1257     inflateEnd(&s);
1258     HTFinishDisplayPartial();
1259     return rv;
1260 }
1261 #endif /* USE_ZLIB */
1262 
1263 #ifdef USE_BZLIB
1264 /*	Push data from a bzip file pointer down a stream
1265  *	-------------------------------------
1266  *
1267  *   This routine is responsible for creating and PRESENTING any
1268  *   graphic (or other) objects described by the file.
1269  *
1270  *
1271  *  State of file and target stream on entry:
1272  *		      BZFILE (bzfp) assumed open (should have bzipped content),
1273  *		      target (sink) assumed valid.
1274  *
1275  *  Return values:
1276  *	HT_INTERRUPTED  Interruption after some data read.
1277  *	HT_PARTIAL_CONTENT	Error after some data read.
1278  *	-1		Error before any data read.
1279  *	HT_LOADED	Normal end of file indication on reading.
1280  *
1281  *  State of file and target stream on return:
1282  *	always		bzfp still open, target stream still valid.
1283  */
HTBzFileCopy(BZFILE * bzfp,HTStream * sink)1284 static int HTBzFileCopy(BZFILE * bzfp, HTStream *sink)
1285 {
1286     HTStreamClass targetClass;
1287     int status;
1288     off_t bytes;
1289     int bzerrnum;
1290     int rv = HT_OK;
1291 
1292     /*  Push the data down the stream
1293      */
1294     targetClass = *(sink->isa);	/* Copy pointers to procedures */
1295 
1296     /*  read and inflate bzip'd file, and push binary down sink
1297      */
1298     HTReadProgress(bytes = 0, (off_t) 0);
1299     for (;;) {
1300 	status = BZ2_bzread(bzfp, input_buffer, INPUT_BUFFER_SIZE);
1301 	if (status <= 0) {	/* EOF or error */
1302 	    if (status == 0) {
1303 		rv = HT_LOADED;
1304 		break;
1305 	    }
1306 	    CTRACE((tfp, "HTBzFileCopy: Read error, bzread returns %d\n",
1307 		    status));
1308 	    CTRACE((tfp, "bzerror   : %s\n",
1309 		    BZ2_bzerror(bzfp, &bzerrnum)));
1310 	    if (bytes) {
1311 		rv = HT_PARTIAL_CONTENT;
1312 	    } else {
1313 		rv = -1;
1314 	    }
1315 	    break;
1316 	}
1317 
1318 	(*targetClass.put_block) (sink, input_buffer, status);
1319 	bytes += status;
1320 	HTReadProgress(bytes, (off_t) -1);
1321 	HTDisplayPartial();
1322 
1323 	if (HTCheckForInterrupt()) {
1324 	    _HTProgress(TRANSFER_INTERRUPTED);
1325 	    rv = HT_INTERRUPTED;
1326 	    break;
1327 	}
1328     }				/* next bufferload */
1329 
1330     HTFinishDisplayPartial();
1331     return rv;
1332 }
1333 #endif /* USE_BZLIB */
1334 
1335 /*	Push data from a socket down a stream STRIPPING CR
1336  *	--------------------------------------------------
1337  *
1338  *   This routine is responsible for creating and PRESENTING any
1339  *   graphic (or other) objects described by the socket.
1340  *
1341  *   The file number given is assumed to be a TELNET stream ie containing
1342  *   CRLF at the end of lines which need to be stripped to LF for unix
1343  *   when the format is textual.
1344  *
1345  */
HTCopyNoCR(HTParentAnchor * anchor GCC_UNUSED,int file_number,HTStream * sink)1346 void HTCopyNoCR(HTParentAnchor *anchor GCC_UNUSED,
1347 		int file_number,
1348 		HTStream *sink)
1349 {
1350     HTStreamClass targetClass;
1351     int character;
1352 
1353     /*  Push the data, ignoring CRLF, down the stream
1354      */
1355     targetClass = *(sink->isa);	/* Copy pointers to procedures */
1356 
1357     /*
1358      * Push text from telnet socket down sink
1359      *
1360      * @@@@@ To push strings could be faster?  (especially is we cheat and
1361      * don't ignore CR!  :-}
1362      */
1363     HTInitInput(file_number);
1364     for (;;) {
1365 	character = HTGetCharacter();
1366 	if (character == EOF)
1367 	    break;
1368 	(*targetClass.put_character) (sink, (char) character);
1369     }
1370 }
1371 
1372 /*	Parse a socket given format and file number
1373  *
1374  *   This routine is responsible for creating and PRESENTING any
1375  *   graphic (or other) objects described by the file.
1376  *
1377  *   The file number given is assumed to be a TELNET stream ie containing
1378  *   CRLF at the end of lines which need to be stripped to LF for unix
1379  *   when the format is textual.
1380  *
1381  *  State of socket and target stream on entry:
1382  *			socket (file_number) assumed open,
1383  *			target (sink) usually NULL (will call stream stack).
1384  *
1385  *  Return values:
1386  *	HT_INTERRUPTED  Interruption or error after some data received.
1387  *	-501		Stream stack failed (cannot present or convert).
1388  *	-2		Unexpected disconnect before any data received.
1389  *	-1		Stream stack failed (cannot present or convert), or
1390  *			Interruption or error before any data received, or
1391  *			(UNIX) other read error before any data received, or
1392  *			download cancelled.
1393  *	HT_LOADED	Normal close of socket (end of file indication
1394  *			received), or
1395  *			unexpected disconnect after some data received, or
1396  *			other read error after some data received, or
1397  *			(not UNIX) other read error before any data received.
1398  *
1399  *  State of socket and target stream on return depends on return value:
1400  *	HT_INTERRUPTED	socket still open, target aborted.
1401  *	-501		socket still open, target stream NULL.
1402  *	-2		socket still open, target freed.
1403  *	-1		socket still open, target stream aborted or NULL.
1404  *	otherwise	socket closed,	target stream freed.
1405  */
HTParseSocket(HTFormat rep_in,HTFormat format_out,HTParentAnchor * anchor,int file_number,HTStream * sink)1406 int HTParseSocket(HTFormat rep_in,
1407 		  HTFormat format_out,
1408 		  HTParentAnchor *anchor,
1409 		  int file_number,
1410 		  HTStream *sink)
1411 {
1412     HTStream *stream;
1413     HTStreamClass targetClass;
1414     int rv;
1415 
1416     stream = HTStreamStack(rep_in, format_out, sink, anchor);
1417 
1418     if (!stream) {
1419 	char *buffer = 0;
1420 
1421 	if (LYCancelDownload) {
1422 	    LYCancelDownload = FALSE;
1423 	    return -1;
1424 	}
1425 	HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
1426 		   HTAtom_name(rep_in), HTAtom_name(format_out));
1427 	CTRACE((tfp, "HTFormat: %s\n", buffer));
1428 	rv = HTLoadError(sink, 501, buffer);	/* returns -501 */
1429 	FREE(buffer);
1430     } else {
1431 	/*
1432 	 * Push the data, don't worry about CRLF we can strip them later.
1433 	 */
1434 	targetClass = *(stream->isa);	/* Copy pointers to procedures */
1435 	rv = HTCopy(anchor, file_number, NULL, stream);
1436 	if (rv != -1 && rv != HT_INTERRUPTED)
1437 	    (*targetClass._free) (stream);
1438     }
1439     return rv;
1440     /* Originally:  full: HT_LOADED;  partial: HT_INTERRUPTED;  no bytes: -1 */
1441 }
1442 
1443 /*	Parse a file given format and file pointer
1444  *
1445  *   This routine is responsible for creating and PRESENTING any
1446  *   graphic (or other) objects described by the file.
1447  *
1448  *   The file number given is assumed to be a TELNET stream ie containing
1449  *   CRLF at the end of lines which need to be stripped to \n for unix
1450  *   when the format is textual.
1451  *
1452  *  State of file and target stream on entry:
1453  *			FILE* (fp) assumed open,
1454  *			target (sink) usually NULL (will call stream stack).
1455  *
1456  *  Return values:
1457  *	-501		Stream stack failed (cannot present or convert).
1458  *	-1		Download cancelled.
1459  *	HT_NO_DATA	Error before any data read.
1460  *	HT_PARTIAL_CONTENT	Interruption or error after some data read.
1461  *	HT_LOADED	Normal end of file indication on reading.
1462  *
1463  *  State of file and target stream on return:
1464  *	always		fp still open; target freed, aborted, or NULL.
1465  */
HTParseFile(HTFormat rep_in,HTFormat format_out,HTParentAnchor * anchor,FILE * fp,HTStream * sink)1466 int HTParseFile(HTFormat rep_in,
1467 		HTFormat format_out,
1468 		HTParentAnchor *anchor,
1469 		FILE *fp,
1470 		HTStream *sink)
1471 {
1472     HTStream *stream;
1473     HTStreamClass targetClass;
1474     int rv;
1475     int result;
1476 
1477     if (fp == NULL) {
1478 	result = HT_LOADED;
1479     } else {
1480 	stream = HTStreamStack(rep_in, format_out, sink, anchor);
1481 
1482 	if (!stream || !stream->isa) {
1483 	    char *buffer = 0;
1484 
1485 	    if (LYCancelDownload) {
1486 		LYCancelDownload = FALSE;
1487 		result = -1;
1488 	    } else {
1489 		HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
1490 			   HTAtom_name(rep_in), HTAtom_name(format_out));
1491 		CTRACE((tfp, "HTFormat(in HTParseFile): %s\n", buffer));
1492 		rv = HTLoadError(sink, 501, buffer);
1493 		FREE(buffer);
1494 		result = rv;
1495 	    }
1496 	} else {
1497 
1498 	    /*
1499 	     * Push the data down the stream
1500 	     *
1501 	     * @@ Bug:  This decision ought to be made based on "encoding"
1502 	     * rather than on content-type.  @@@ When we handle encoding.  The
1503 	     * current method smells anyway.
1504 	     */
1505 	    targetClass = *(stream->isa);	/* Copy pointers to procedures */
1506 	    rv = HTFileCopy(fp, stream);
1507 	    if (rv == -1 || rv == HT_INTERRUPTED) {
1508 		(*targetClass._abort) (stream, NULL);
1509 	    } else {
1510 		(*targetClass._free) (stream);
1511 	    }
1512 
1513 	    if (rv == -1) {
1514 		result = HT_NO_DATA;
1515 	    } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) {
1516 		result = HT_PARTIAL_CONTENT;
1517 	    } else {
1518 		result = HT_LOADED;
1519 	    }
1520 	}
1521     }
1522     return result;
1523 }
1524 
1525 #ifdef USE_SOURCE_CACHE
1526 /*	Parse a document in memory given format and memory block pointer
1527  *
1528  *   This routine is responsible for creating and PRESENTING any
1529  *   graphic (or other) objects described by the file.
1530  *
1531  *  State of memory and target stream on entry:
1532  *			HTChunk* (chunk) assumed valid,
1533  *			target (sink) usually NULL (will call stream stack).
1534  *
1535  *  Return values:
1536  *	-501		Stream stack failed (cannot present or convert).
1537  *	HT_LOADED	All data sent.
1538  *
1539  *  State of memory and target stream on return:
1540  *	always		chunk unchanged; target freed, aborted, or NULL.
1541  */
HTParseMem(HTFormat rep_in,HTFormat format_out,HTParentAnchor * anchor,HTChunk * chunk,HTStream * sink)1542 int HTParseMem(HTFormat rep_in,
1543 	       HTFormat format_out,
1544 	       HTParentAnchor *anchor,
1545 	       HTChunk *chunk,
1546 	       HTStream *sink)
1547 {
1548     HTStream *stream;
1549     HTStreamClass targetClass;
1550     int rv;
1551     int result;
1552 
1553     stream = HTStreamStack(rep_in, format_out, sink, anchor);
1554     if (!stream || !stream->isa) {
1555 	char *buffer = 0;
1556 
1557 	HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
1558 		   HTAtom_name(rep_in), HTAtom_name(format_out));
1559 	CTRACE((tfp, "HTFormat(in HTParseMem): %s\n", buffer));
1560 	rv = HTLoadError(sink, 501, buffer);
1561 	FREE(buffer);
1562 	result = rv;
1563     } else {
1564 
1565 	/* Push the data down the stream
1566 	 */
1567 	targetClass = *(stream->isa);
1568 	(void) HTMemCopy(chunk, stream);
1569 	(*targetClass._free) (stream);
1570 	result = HT_LOADED;
1571     }
1572     return result;
1573 }
1574 #endif
1575 
1576 #ifdef USE_ZLIB
HTCloseGzFile(gzFile gzfp)1577 static int HTCloseGzFile(gzFile gzfp)
1578 {
1579     int gzres;
1580 
1581     if (gzfp == NULL)
1582 	return 0;
1583     gzres = gzclose(gzfp);
1584     if (TRACE) {
1585 	if (gzres == Z_ERRNO) {
1586 	    perror("gzclose   ");
1587 	} else if (gzres != Z_OK) {
1588 	    CTRACE((tfp, "gzclose   : error number %d\n", gzres));
1589 	}
1590     }
1591     return (gzres);
1592 }
1593 
1594 /*	HTParseGzFile
1595  *
1596  *  State of file and target stream on entry:
1597  *			gzFile (gzfp) assumed open,
1598  *			target (sink) usually NULL (will call stream stack).
1599  *
1600  *  Return values:
1601  *	-501		Stream stack failed (cannot present or convert).
1602  *	-1		Download cancelled.
1603  *	HT_NO_DATA	Error before any data read.
1604  *	HT_PARTIAL_CONTENT	Interruption or error after some data read.
1605  *	HT_LOADED	Normal end of file indication on reading.
1606  *
1607  *  State of file and target stream on return:
1608  *	always		gzfp closed; target freed, aborted, or NULL.
1609  */
HTParseGzFile(HTFormat rep_in,HTFormat format_out,HTParentAnchor * anchor,gzFile gzfp,HTStream * sink)1610 int HTParseGzFile(HTFormat rep_in,
1611 		  HTFormat format_out,
1612 		  HTParentAnchor *anchor,
1613 		  gzFile gzfp,
1614 		  HTStream *sink)
1615 {
1616     HTStream *stream;
1617     HTStreamClass targetClass;
1618     int rv;
1619     int result;
1620 
1621     stream = HTStreamStack(rep_in, format_out, sink, anchor);
1622 
1623     if (!stream || !stream->isa) {
1624 	char *buffer = 0;
1625 
1626 	HTCloseGzFile(gzfp);
1627 	if (LYCancelDownload) {
1628 	    LYCancelDownload = FALSE;
1629 	    result = -1;
1630 	} else {
1631 	    HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
1632 		       HTAtom_name(rep_in), HTAtom_name(format_out));
1633 	    CTRACE((tfp, "HTFormat(in HTParseGzFile): %s\n", buffer));
1634 	    rv = HTLoadError(sink, 501, buffer);
1635 	    FREE(buffer);
1636 	    result = rv;
1637 	}
1638     } else {
1639 
1640 	/*
1641 	 * Push the data down the stream
1642 	 *
1643 	 * @@ Bug:  This decision ought to be made based on "encoding" rather than
1644 	 * on content-type.  @@@ When we handle encoding.  The current method
1645 	 * smells anyway.
1646 	 */
1647 	targetClass = *(stream->isa);	/* Copy pointers to procedures */
1648 	rv = HTGzFileCopy(gzfp, stream);
1649 	if (rv == -1 || rv == HT_INTERRUPTED) {
1650 	    (*targetClass._abort) (stream, NULL);
1651 	} else {
1652 	    (*targetClass._free) (stream);
1653 	}
1654 
1655 	HTCloseGzFile(gzfp);
1656 	if (rv == -1) {
1657 	    result = HT_NO_DATA;
1658 	} else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) {
1659 	    result = HT_PARTIAL_CONTENT;
1660 	} else {
1661 	    result = HT_LOADED;
1662 	}
1663     }
1664     return result;
1665 }
1666 
1667 /*	HTParseZzFile
1668  *
1669  *  State of file and target stream on entry:
1670  *			FILE (zzfp) assumed open,
1671  *			target (sink) usually NULL (will call stream stack).
1672  *
1673  *  Return values:
1674  *	-501		Stream stack failed (cannot present or convert).
1675  *	-1		Download cancelled.
1676  *	HT_NO_DATA	Error before any data read.
1677  *	HT_PARTIAL_CONTENT	Interruption or error after some data read.
1678  *	HT_LOADED	Normal end of file indication on reading.
1679  *
1680  *  State of file and target stream on return:
1681  *	always		zzfp closed; target freed, aborted, or NULL.
1682  */
HTParseZzFile(HTFormat rep_in,HTFormat format_out,HTParentAnchor * anchor,FILE * zzfp,HTStream * sink)1683 int HTParseZzFile(HTFormat rep_in,
1684 		  HTFormat format_out,
1685 		  HTParentAnchor *anchor,
1686 		  FILE *zzfp,
1687 		  HTStream *sink)
1688 {
1689     HTStream *stream;
1690     HTStreamClass targetClass;
1691     int rv;
1692     int result;
1693 
1694     stream = HTStreamStack(rep_in, format_out, sink, anchor);
1695 
1696     if (!stream || !stream->isa) {
1697 	char *buffer = 0;
1698 
1699 	fclose(zzfp);
1700 	if (LYCancelDownload) {
1701 	    LYCancelDownload = FALSE;
1702 	    result = -1;
1703 	} else {
1704 	    HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
1705 		       HTAtom_name(rep_in), HTAtom_name(format_out));
1706 	    CTRACE((tfp, "HTFormat(in HTParseGzFile): %s\n", buffer));
1707 	    rv = HTLoadError(sink, 501, buffer);
1708 	    FREE(buffer);
1709 	    result = rv;
1710 	}
1711     } else {
1712 
1713 	/*
1714 	 * Push the data down the stream
1715 	 *
1716 	 * @@ Bug:  This decision ought to be made based on "encoding" rather than
1717 	 * on content-type.  @@@ When we handle encoding.  The current method
1718 	 * smells anyway.
1719 	 */
1720 	targetClass = *(stream->isa);	/* Copy pointers to procedures */
1721 	rv = HTZzFileCopy(zzfp, stream);
1722 	if (rv == -1 || rv == HT_INTERRUPTED) {
1723 	    (*targetClass._abort) (stream, NULL);
1724 	} else {
1725 	    (*targetClass._free) (stream);
1726 	}
1727 
1728 	fclose(zzfp);
1729 	if (rv == -1) {
1730 	    result = HT_NO_DATA;
1731 	} else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) {
1732 	    result = HT_PARTIAL_CONTENT;
1733 	} else {
1734 	    result = HT_LOADED;
1735 	}
1736     }
1737     return result;
1738 }
1739 #endif /* USE_ZLIB */
1740 
1741 #ifdef USE_BZLIB
HTCloseBzFile(BZFILE * bzfp)1742 static void HTCloseBzFile(BZFILE * bzfp)
1743 {
1744     if (bzfp)
1745 	BZ2_bzclose(bzfp);
1746 }
1747 
1748 /*	HTParseBzFile
1749  *
1750  *  State of file and target stream on entry:
1751  *			bzFile (bzfp) assumed open,
1752  *			target (sink) usually NULL (will call stream stack).
1753  *
1754  *  Return values:
1755  *	-501		Stream stack failed (cannot present or convert).
1756  *	-1		Download cancelled.
1757  *	HT_NO_DATA	Error before any data read.
1758  *	HT_PARTIAL_CONTENT	Interruption or error after some data read.
1759  *	HT_LOADED	Normal end of file indication on reading.
1760  *
1761  *  State of file and target stream on return:
1762  *	always		bzfp closed; target freed, aborted, or NULL.
1763  */
HTParseBzFile(HTFormat rep_in,HTFormat format_out,HTParentAnchor * anchor,BZFILE * bzfp,HTStream * sink)1764 int HTParseBzFile(HTFormat rep_in,
1765 		  HTFormat format_out,
1766 		  HTParentAnchor *anchor,
1767 		  BZFILE * bzfp,
1768 		  HTStream *sink)
1769 {
1770     HTStream *stream;
1771     HTStreamClass targetClass;
1772     int rv;
1773     int result;
1774 
1775     stream = HTStreamStack(rep_in, format_out, sink, anchor);
1776 
1777     if (!stream || !stream->isa) {
1778 	char *buffer = 0;
1779 
1780 	HTCloseBzFile(bzfp);
1781 	if (LYCancelDownload) {
1782 	    LYCancelDownload = FALSE;
1783 	    result = -1;
1784 	} else {
1785 	    HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
1786 		       HTAtom_name(rep_in), HTAtom_name(format_out));
1787 	    CTRACE((tfp, "HTFormat(in HTParseBzFile): %s\n", buffer));
1788 	    rv = HTLoadError(sink, 501, buffer);
1789 	    FREE(buffer);
1790 	    result = rv;
1791 	}
1792     } else {
1793 
1794 	/*
1795 	 * Push the data down the stream
1796 	 *
1797 	 * @@ Bug:  This decision ought to be made based on "encoding" rather than
1798 	 * on content-type.  @@@ When we handle encoding.  The current method
1799 	 * smells anyway.
1800 	 */
1801 	targetClass = *(stream->isa);	/* Copy pointers to procedures */
1802 	rv = HTBzFileCopy(bzfp, stream);
1803 	if (rv == -1 || rv == HT_INTERRUPTED) {
1804 	    (*targetClass._abort) (stream, NULL);
1805 	} else {
1806 	    (*targetClass._free) (stream);
1807 	}
1808 
1809 	HTCloseBzFile(bzfp);
1810 	if (rv == -1) {
1811 	    result = HT_NO_DATA;
1812 	} else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) {
1813 	    result = HT_PARTIAL_CONTENT;
1814 	} else {
1815 	    result = HT_LOADED;
1816 	}
1817     }
1818     return result;
1819 }
1820 #endif /* USE_BZLIB */
1821 
1822 /*	Converter stream: Network Telnet to internal character text
1823  *	-----------------------------------------------------------
1824  *
1825  *	The input is assumed to be in ASCII, with lines delimited
1826  *	by (13,10) pairs, These pairs are converted into (CR,LF)
1827  *	pairs in the local representation.  The (CR,LF) sequence
1828  *	when found is changed to a '\n' character, the internal
1829  *	C representation of a new line.
1830  */
1831 
NetToText_put_character(HTStream * me,int net_char)1832 static void NetToText_put_character(HTStream *me, int net_char)
1833 {
1834     char c = (char) FROMASCII(net_char);
1835 
1836     if (me->had_cr) {
1837 	if (c == LF) {
1838 	    me->sink->isa->put_character(me->sink, '\n');	/* Newline */
1839 	    me->had_cr = NO;
1840 	    return;
1841 	} else {
1842 	    me->sink->isa->put_character(me->sink, CR);		/* leftover */
1843 	}
1844     }
1845     me->had_cr = (BOOL) (c == CR);
1846     if (!me->had_cr)
1847 	me->sink->isa->put_character(me->sink, c);	/* normal */
1848 }
1849 
NetToText_put_string(HTStream * me,const char * s)1850 static void NetToText_put_string(HTStream *me, const char *s)
1851 {
1852     const char *p;
1853 
1854     for (p = s; *p; p++)
1855 	NetToText_put_character(me, *p);
1856 }
1857 
NetToText_put_block(HTStream * me,const char * s,int l)1858 static void NetToText_put_block(HTStream *me, const char *s, int l)
1859 {
1860     const char *p;
1861 
1862     for (p = s; p < (s + l); p++)
1863 	NetToText_put_character(me, *p);
1864 }
1865 
NetToText_free(HTStream * me)1866 static void NetToText_free(HTStream *me)
1867 {
1868     (me->sink->isa->_free) (me->sink);	/* Close rest of pipe */
1869     FREE(me);
1870 }
1871 
NetToText_abort(HTStream * me,HTError e)1872 static void NetToText_abort(HTStream *me, HTError e)
1873 {
1874     me->sink->isa->_abort(me->sink, e);		/* Abort rest of pipe */
1875     FREE(me);
1876 }
1877 
1878 /*	The class structure
1879 */
1880 static HTStreamClass NetToTextClass =
1881 {
1882     "NetToText",
1883     NetToText_free,
1884     NetToText_abort,
1885     NetToText_put_character,
1886     NetToText_put_string,
1887     NetToText_put_block
1888 };
1889 
1890 /*	The creation method
1891 */
HTNetToText(HTStream * sink)1892 HTStream *HTNetToText(HTStream *sink)
1893 {
1894     HTStream *me = typecalloc(HTStream);
1895 
1896     if (me == NULL)
1897 	outofmem(__FILE__, "NetToText");
1898 
1899     me->isa = &NetToTextClass;
1900 
1901     me->had_cr = NO;
1902     me->sink = sink;
1903     return me;
1904 }
1905 
1906 static HTStream HTBaseStreamInstance;	/* Made static */
1907 
1908 /*
1909  *	ERROR STREAM
1910  *	------------
1911  *	There is only one error stream shared by anyone who wants a
1912  *	generic error returned from all stream methods.
1913  */
HTErrorStream_put_character(HTStream * me GCC_UNUSED,int c GCC_UNUSED)1914 static void HTErrorStream_put_character(HTStream *me GCC_UNUSED, int c GCC_UNUSED)
1915 {
1916     LYCancelDownload = TRUE;
1917 }
1918 
HTErrorStream_put_string(HTStream * me GCC_UNUSED,const char * s)1919 static void HTErrorStream_put_string(HTStream *me GCC_UNUSED, const char *s)
1920 {
1921     if (s && *s)
1922 	LYCancelDownload = TRUE;
1923 }
1924 
HTErrorStream_write(HTStream * me GCC_UNUSED,const char * s,int l)1925 static void HTErrorStream_write(HTStream *me GCC_UNUSED, const char *s, int l)
1926 {
1927     if (l && s)
1928 	LYCancelDownload = TRUE;
1929 }
1930 
HTErrorStream_free(HTStream * me GCC_UNUSED)1931 static void HTErrorStream_free(HTStream *me GCC_UNUSED)
1932 {
1933     return;
1934 }
1935 
HTErrorStream_abort(HTStream * me GCC_UNUSED,HTError e GCC_UNUSED)1936 static void HTErrorStream_abort(HTStream *me GCC_UNUSED, HTError e GCC_UNUSED)
1937 {
1938     return;
1939 }
1940 
1941 static const HTStreamClass HTErrorStreamClass =
1942 {
1943     "ErrorStream",
1944     HTErrorStream_free,
1945     HTErrorStream_abort,
1946     HTErrorStream_put_character,
1947     HTErrorStream_put_string,
1948     HTErrorStream_write
1949 };
1950 
HTErrorStream(void)1951 HTStream *HTErrorStream(void)
1952 {
1953     CTRACE((tfp, "ErrorStream. Created\n"));
1954     HTBaseStreamInstance.isa = &HTErrorStreamClass;	/* The rest is random */
1955     return &HTBaseStreamInstance;
1956 }
1957