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