1 /*								       HTMIME.c
2 **	MIME MESSAGE PARSE
3 **
4 **	(c) COPYRIGHT MIT 1995.
5 **	Please first read the full copyright statement in the file COPYRIGH.
6 **	@(#) $Id$
7 **
8 **	This is RFC 1341-specific code.
9 **	The input stream pushed into this parser is assumed to be
10 **	stripped on CRs, ie lines end with LF, not CR LF.
11 **	(It is easy to change this except for the body part where
12 **	conversion can be slow.)
13 **
14 ** History:
15 **	   Feb 92	Written Tim Berners-Lee, CERN
16 **	 8 Jul 94  FM	Insulate free() from _free structure element.
17 **	14 Mar 95  HFN	Now using response for storing data. No more `\n',
18 **			static buffers etc.
19 */
20 
21 /* Library include files */
22 #include "wwwsys.h"
23 #include "WWWUtil.h"
24 #include "WWWCore.h"
25 #include "HTReqMan.h"
26 #include "HTNetMan.h"
27 #include "HTHeader.h"
28 #include "HTWWWStr.h"
29 
30 #ifndef NO_CACHE
31 #include "HTTee.h"
32 #include "HTConLen.h"
33 #include "HTMerge.h"
34 #include "WWWCache.h"
35 #endif
36 
37 #include "HTMIME.h"					 /* Implemented here */
38 
39 typedef enum _HTMIMEMode {
40     HT_MIME_HEADER	= 0x1,
41     HT_MIME_FOOTER	= 0x2,
42     HT_MIME_PARTIAL	= 0x4,
43     HT_MIME_CONT	= 0x8,
44     HT_MIME_UPGRADE	= 0x10
45 } HTMIMEMode;
46 
47 struct _HTStream {
48     const HTStreamClass *	isa;
49     HTRequest *			request;
50     HTResponse *		response;
51     HTNet *			net;
52     HTStream *			target;
53     HTConverter *		save_stream;
54     HTFormat			target_format;
55     HTChunk *			token;
56     HTChunk *			value;
57     int				hash;
58     HTEOLState			EOLstate;
59     HTMIMEMode			mode;
60     BOOL			transparent;
61     BOOL			haveToken;
62     BOOL			hasBody;
63 };
64 
65 PRIVATE HTConverter * LocalSaveStream = NULL; /* Where to save unknown stuff */
66 
67 /* ------------------------------------------------------------------------- */
68 
pumpData(HTStream * me)69 PRIVATE int pumpData (HTStream * me)
70 {
71     HTRequest * request = me->request;
72     HTResponse * response = me->response;
73     HTFormat format = HTResponse_format(response);
74     HTList * te = HTResponse_transfer(response);
75     HTList * ce = HTResponse_encoding(response);
76     long length = HTResponse_length(response);
77     HTStream * BlackHole = HTBlackHole();
78     BOOL savestream = NO;
79     me->transparent = YES;		  /* Pump rest of data right through */
80 
81     /*
82     **  Cache the metainformation in the anchor object by copying
83     **  it from the response object. This we do regardless if
84     **  we have a persistent cache or not as the memory cache will
85     **  use it as well. If we are updating a cache entry using
86     **  byte ranges then we already have the metainformation and
87     **  hence we can ignore the new one as it'd better be the same.
88     */
89     if (!(me->mode & HT_MIME_PARTIAL) &&
90 	HTResponse_isCachable(me->response) != HT_NO_CACHE)
91 	HTAnchor_update(HTRequest_anchor(request), me->response);
92 
93     /*
94     **  If we asked only to read the header or footer or we used a HEAD
95     **  method then we stop here as we don't expect any body part.
96     */
97     if (me->mode & (HT_MIME_HEADER | HT_MIME_FOOTER) ||
98 	HTRequest_method(request) == METHOD_HEAD) {
99         HTAlertCallback * cbf = HTAlert_find(HT_PROG_DONE);
100         if (cbf) (*cbf)(request, HT_PROG_DONE, HT_MSG_NULL, NULL, NULL, NULL);
101         return HT_LOADED;
102     }
103 
104     /*
105     **  If we are paring a 1xx response then return HT_CONTINUE
106     */
107     if (me->mode & HT_MIME_CONT)
108 	return HT_CONTINUE;
109 
110     /*
111     **  If we get a 101 Protocol Switch then we are done here
112     **  but not done with the response (which we don't know
113     **  how to go about parsing
114     */
115     if (me->mode & HT_MIME_UPGRADE) {
116 	me->hasBody = YES;
117 	return HT_OK;
118     }
119 
120     /*
121     **  If there is no content-length, no transfer encoding and no
122     **  content type then we assume that there is no body part in
123     **  the message and we can return HT_LOADED
124     */
125     {
126 	HTHost * host = HTNet_host(me->net);
127 	if (length<0 && te==NULL &&
128 	    HTHost_isPersistent(host) && !HTHost_closeNotification(host)) {
129 	    if (format != WWW_UNKNOWN) {
130 		HTTRACE(STREAM_TRACE, "MIME Parser. BAD - there seems to be a body but no length. This must be an HTTP/1.0 server pretending that it is HTTP/1.1\n");
131 		HTHost_setCloseNotification(host, YES);
132 	    } else {
133                 HTAlertCallback * cbf = HTAlert_find(HT_PROG_DONE);
134                 if (cbf) (*cbf)(request, HT_PROG_DONE, HT_MSG_NULL, NULL, NULL, NULL);
135 		HTTRACE(STREAM_TRACE, "MIME Parser. No body in this message\n");
136 		return HT_LOADED;
137 	    }
138 	}
139     }
140 
141     /*
142     **  Deal with the body
143     */
144     me->hasBody = YES;
145 
146     /*
147     **  Handle any Content Type
148     */
149     if (!(me->mode & HT_MIME_PARTIAL) &&
150 	(format != WWW_UNKNOWN || length > 0 || te)) {
151 	HTStream * target;
152 	HTTRACE(STREAM_TRACE, "Building.... C-T stack from %s to %s\n" _
153 				  HTAtom_name(format) _
154 				  HTAtom_name(me->target_format));
155 	if ((target = HTStreamStack(format, me->target_format,
156 				    me->target, request, YES))==BlackHole) {
157 	    if (!savestream) {
158                 if (me->target) (*me->target->isa->abort)(me->target, NULL);
159                 me->target = me->save_stream(request, NULL,
160 					     format, me->target_format, me->target);
161 		savestream = YES;
162 	    }
163 	} else
164 	    me->target = target;
165     }
166 
167     /*
168     **  Handle any Content Encodings
169     */
170     HTTRACE(STREAM_TRACE, "Building.... Content-Decoding stack\n");
171     if (ce) {
172 	HTStream * target = HTContentDecodingStack(ce, me->target, request, NULL);
173 	if (target == BlackHole) {
174 	    if (!savestream) {
175 		if (me->target) (*me->target->isa->abort)(me->target, NULL);
176                 me->target = me->save_stream(request, NULL,
177 					     format, me->target_format, me->target);
178 		savestream = YES;
179 	    }
180 	} else
181 	    me->target = target;
182     }
183 
184     /*
185     **  Can we cache the data object? If so then create a T stream and hook it
186     **  into the stream pipe. We do it before the transfer decoding so that we
187     **  don't have to deal with that when we retrieve the object from cache.
188     **  If we are appending to a cache entry then use a different stream than
189     **  if creating a new entry.
190     */
191 #ifndef NO_CACHE
192     if (HTCacheMode_enabled()) {
193 	if (me->mode & HT_MIME_PARTIAL) {
194 	    HTStream * append = HTStreamStack(WWW_CACHE_APPEND,
195 					      me->target_format,
196 					      me->target, request, NO);
197 	    if (append) me->target = HTTee(me->target, append, NULL);
198 	} else if (HTResponse_isCachable(me->response) == HT_CACHE_ALL) {
199 	    HTStream * cache = HTStreamStack(WWW_CACHE, me->target_format,
200 					     me->target, request, NO);
201 	    if (cache) me->target = HTTee(me->target, cache, NULL);
202 	}
203     }
204 #endif
205 
206     /*
207     **  Handle any Transfer Encodings
208     */
209     HTTRACE(STREAM_TRACE, "Building.... Transfer-Decoding stack\n");
210     if (te) {
211 	HTStream * target = HTTransferDecodingStack(te, me->target, request, NULL);
212 	if (target == BlackHole) {
213 	    if (!savestream) {
214 		if (me->target) (*me->target->isa->abort)(me->target, NULL);
215                 me->target = me->save_stream(request, NULL,
216 					     format, me->target_format, me->target);
217 		savestream = YES;
218 	    }
219 	} else
220 	    me->target = target;
221     }
222 
223 
224     /*
225     ** If we for some reason couldn't find a target stream
226     */
227     if (!me->target) me->target = HTBlackHole();
228     return HT_OK;
229 }
230 
231 /* _dispatchParsers
232  * call request's MIME header parser. Use global parser if no
233  * appropriate one is found for request.
234 */
_dispatchParsers(HTRequest * req,char * token,char * value)235 PRIVATE int _dispatchParsers (HTRequest * req, char * token, char * value)
236 {
237     int status;
238     BOOL found = NO;
239     BOOL local = NO;
240     HTMIMEParseSet * parseSet;
241 
242     /* In case we get an empty header consisting of a CRLF, we fall thru */
243     HTTRACE(STREAM_TRACE, "MIME header. %s: %s\n" _
244 			      token ? token : "<null>" _
245 			      value ? value : "<null>");
246     if (!token) return HT_OK;			    /* Ignore noop token */
247 
248     /*
249     ** Search the local set of MIME parsers
250     */
251     if ((parseSet = HTRequest_MIMEParseSet(req, &local)) != NULL) {
252         status = HTMIMEParseSet_dispatch(parseSet, req,
253 					 token, value, &found);
254 	if (found) return status;
255     }
256 
257     /*
258     ** Search the global set of MIME parsers
259     */
260     if (local==NO && (parseSet = HTHeader_MIMEParseSet()) != NULL) {
261 	status = HTMIMEParseSet_dispatch(parseSet, req,
262 					 token, value, &found);
263 	if (found) return status;
264     }
265 
266     return HT_OK;
267 }
268 
269 /* _stream2dispatchParsers - extracts the arguments from a
270  * MIME stream before calling the generic _dispatchParser
271  * function.
272  */
_stream2dispatchParsers(HTStream * me)273 PRIVATE int _stream2dispatchParsers (HTStream * me)
274 {
275     char * token = HTChunk_data(me->token);
276     char * value = HTChunk_data(me->value);
277 
278     /* In case we get an empty header consisting of a CRLF, we fall thru */
279     HTTRACE(STREAM_TRACE, "MIME header. %s: %s\n" _
280 			      token ? token : "<null>" _
281 			      value ? value : "<null>");
282     if (!token) return HT_OK;			    /* Ignore noop token */
283 
284     /*
285     ** Remember the original header
286     */
287     HTResponse_addHeader(me->response, token, value);
288 
289     /* call the parsers to set the headers */
290     return (_dispatchParsers (me->request, token, value));
291 }
292 
293 /*
294 **	Header is terminated by CRCR, LFLF, CRLFLF, CRLFCRLF
295 **	Folding is either of CF LWS, LF LWS, CRLF LWS
296 */
HTMIME_put_block(HTStream * me,const char * b,int l)297 PRIVATE int HTMIME_put_block (HTStream * me, const char * b, int l)
298 {
299     const char * start = b;
300     const char * end = start;
301     const char * value = HTChunk_size(me->value) > 0 ? b : NULL;
302     int length = l;
303     int status;
304 
305     while (!me->transparent) {
306 	if (me->EOLstate == EOL_FCR) {
307 	    if (*b == CR)				    /* End of header */
308 	        me->EOLstate = EOL_END;
309 	    else if (*b == LF)			   	     /* CRLF */
310 		me->EOLstate = EOL_FLF;
311 	    else if (isspace((int) *b))			   /* Folding: CR SP */
312 	        me->EOLstate = EOL_FOLD;
313 	    else						 /* New line */
314 	        me->EOLstate = EOL_LINE;
315 	} else if (me->EOLstate == EOL_FLF) {
316 	    if (*b == CR)				/* LF CR or CR LF CR */
317 		me->EOLstate = EOL_SCR;
318 	    else if (*b == LF)				    /* End of header */
319 	        me->EOLstate = EOL_END;
320 	    else if (isspace((int) *b))	       /* Folding: LF SP or CR LF SP */
321 		me->EOLstate = EOL_FOLD;
322 	    else						/* New line */
323 		me->EOLstate = EOL_LINE;
324 	} else if (me->EOLstate == EOL_SCR) {
325 	    if (*b==CR || *b==LF)			    /* End of header */
326 	        me->EOLstate = EOL_END;
327 	    else if (isspace((int) *b))	 /* Folding: LF CR SP or CR LF CR SP */
328 		me->EOLstate = EOL_FOLD;
329 	    else						/* New line */
330 		me->EOLstate = EOL_LINE;
331 	} else if (*b == CR)
332 	    me->EOLstate = EOL_FCR;
333 	else if (*b == LF)
334 	    me->EOLstate = EOL_FLF;			       /* Line found */
335 	else {
336 	    if (!me->haveToken) {
337 	        if (*b == ':' || isspace((int) *b)) {
338 		    HTChunk_putb(me->token, start, end-start);
339 		    HTChunk_putc(me->token, '\0');
340 		    me->haveToken = YES;
341 		} else {
342 		    unsigned char ch = *(unsigned char *) b;
343 		    ch = TOLOWER(ch);
344 		    me->hash = (me->hash * 3 + ch) % MIME_HASH_SIZE;
345 		}
346 	    } else if (value == NULL && *b != ':' && !isspace((int) *b))
347 	        value = b;
348 	    end++;
349 	}
350 	switch (me->EOLstate) {
351 	    case EOL_LINE:
352 	    case EOL_END:
353 	    {
354 		int ret = HT_ERROR;
355 		HTChunk_putb(me->value, value, end-value);
356 		HTChunk_putc(me->value, '\0');
357 		ret =  _stream2dispatchParsers(me);
358 		HTNet_addBytesRead(me->net, b-start);
359 		start=b, end=b;
360 		if (me->EOLstate == EOL_END) {		/* EOL_END */
361 		    if (ret == HT_OK) {
362 			b++, l--;
363 			ret = pumpData(me);
364 			HTNet_addBytesRead(me->net, 1);
365 			if (me->mode & (HT_MIME_FOOTER | HT_MIME_CONT)) {
366 			    HTHost_setConsumed(HTNet_host(me->net), length - l);
367 			    return ret;
368                         } else {
369                             HTNet_setHeaderBytesRead(me->net, HTNet_bytesRead(me->net));
370                         }
371 		    }
372 	        } else {				/* EOL_LINE */
373 		    HTChunk_truncate(me->token,0);
374 		    HTChunk_truncate(me->value,0);
375 		    me->haveToken = NO;
376 		    me->hash = 0;
377 		    value = NULL;
378 		}
379 		me->EOLstate = EOL_BEGIN;
380 		if (ret != HT_OK && ret != HT_LOADED) return ret;
381 		break;
382 	    }
383 	    case EOL_FOLD:
384 		me->EOLstate = EOL_BEGIN;
385 	        if (!me->haveToken) {
386 		    HTChunk_putb(me->token, start, end-start);
387 		    HTChunk_putc(me->token, '\0');
388 		    me->haveToken = YES;
389 	        } else if (value) {
390 		    HTChunk_putb(me->value, value, end-value);
391 		    HTChunk_putc(me->value, ' ');
392 		}
393 		start=b, end=b;
394 		break;
395 	    default:
396 	        b++, l--;
397 	        if (!l) {
398 		    BOOL stop = NO;
399 		    if (!me->haveToken) {
400 			/* If empty header then prepare to stop */
401 			if (end-start)
402 			    HTChunk_putb(me->token, start, end-start);
403 			else
404 			    stop = YES;
405 		    } else if (value)
406 		        HTChunk_putb(me->value, value, end-value);
407 		    HTHost_setConsumed(HTNet_host(me->net), length - l);
408 		    return stop ? pumpData(me) : HT_OK;
409 		}
410 	}
411     }
412 
413     if (length != l) HTHost_setConsumed(HTNet_host(me->net), length - l);
414 
415     /*
416     ** Put the rest down the stream without touching the data but make sure
417     ** that we get the correct content length of data. If we have a CL in
418     ** the headers then this stream is responsible for the accountance.
419     */
420     if (me->hasBody) {
421 	HTNet * net = me->net;
422 	/* Check if CL at all - thanks to jwei@hal.com (John Wei) */
423 	long cl = HTResponse_length(me->response);
424 	if (cl >= 0) {
425 	    long bodyRead = HTNet_bytesRead(net) - HTNet_headerBytesRead(net);
426 
427 	    /*
428 	    **  If we have more than we need then just take what belongs to us.
429 	    */
430 	    if (bodyRead + l >= cl) {
431 		int consume = cl - bodyRead;
432 		if ((status = (*me->target->isa->put_block)(me->target, b, consume)) < 0)
433 		    return status;
434                 else {
435                     HTAlertCallback * cbf = HTAlert_find(HT_PROG_DONE);
436                     HTNet_addBytesRead(net, consume);
437                     HTHost_setConsumed(HTNet_host(net), consume);
438                     if (cbf) (*cbf)(me->request, HT_PROG_DONE, HT_MSG_NULL, NULL, NULL, NULL);
439                     return HT_LOADED;
440                 }
441             } else {
442 		if ((status = (*me->target->isa->put_block)(me->target, b, l)) < 0)
443 		    return status;
444 		HTNet_addBytesRead(net, l);
445 		HTHost_setConsumed(HTNet_host(net), l);
446 		return status;
447 	    }
448 	}
449 	return (*me->target->isa->put_block)(me->target, b, l);
450     } else {
451         HTAlertCallback * cbf = HTAlert_find(HT_PROG_DONE);
452         if (cbf) (*cbf)(me->request, HT_PROG_DONE, HT_MSG_NULL, NULL, NULL, NULL);
453     }
454     return HT_LOADED;
455 }
456 
457 
458 /*	Character handling
459 **	------------------
460 */
HTMIME_put_character(HTStream * me,char c)461 PRIVATE int HTMIME_put_character (HTStream * me, char c)
462 {
463     return HTMIME_put_block(me, &c, 1);
464 }
465 
466 
467 /*	String handling
468 **	---------------
469 */
HTMIME_put_string(HTStream * me,const char * s)470 PRIVATE int HTMIME_put_string (HTStream * me, const char * s)
471 {
472     return HTMIME_put_block(me, s, (int) strlen(s));
473 }
474 
475 
476 /*	Flush an stream object
477 **	---------------------
478 */
HTMIME_flush(HTStream * me)479 PRIVATE int HTMIME_flush (HTStream * me)
480 {
481     return me->target ? (*me->target->isa->flush)(me->target) : HT_OK;
482 }
483 
484 /*	Free a stream object
485 **	--------------------
486 */
HTMIME_free(HTStream * me)487 PRIVATE int HTMIME_free (HTStream * me)
488 {
489     int status = HT_OK;
490     if (!me->transparent)
491         if (_stream2dispatchParsers(me) == HT_OK)
492 	    pumpData(me);
493     if (me->target) {
494 	if ((status = (*me->target->isa->_free)(me->target))==HT_WOULD_BLOCK)
495 	    return HT_WOULD_BLOCK;
496     }
497     HTTRACE(PROT_TRACE, "MIME........ FREEING....\n");
498     HTChunk_delete(me->token);
499     HTChunk_delete(me->value);
500     HT_FREE(me);
501     return status;
502 }
503 
504 /*	End writing
505 */
HTMIME_abort(HTStream * me,HTList * e)506 PRIVATE int HTMIME_abort (HTStream * me, HTList * e)
507 {
508     int status = HT_ERROR;
509     if (me->target) status = (*me->target->isa->abort)(me->target, e);
510     HTTRACE(PROT_TRACE, "MIME........ ABORTING...\n");
511     HTChunk_delete(me->token);
512     HTChunk_delete(me->value);
513     HT_FREE(me);
514     return status;
515 }
516 
517 
518 
519 /*	Structured Object Class
520 **	-----------------------
521 */
522 PRIVATE const HTStreamClass HTMIME =
523 {
524 	"MIMEParser",
525 	HTMIME_flush,
526 	HTMIME_free,
527 	HTMIME_abort,
528 	HTMIME_put_character,
529 	HTMIME_put_string,
530 	HTMIME_put_block
531 };
532 
533 
534 /*	MIME header parser stream.
535 **	-------------------------
536 **	This stream parses a complete MIME header and if a content type header
537 **	is found then the stream stack is called. Any left over data is pumped
538 **	right through the stream
539 */
HTMIMEConvert(HTRequest * request,void * param,HTFormat input_format,HTFormat output_format,HTStream * output_stream)540 PUBLIC HTStream* HTMIMEConvert (HTRequest *	request,
541 				void *		param,
542 				HTFormat	input_format,
543 				HTFormat	output_format,
544 				HTStream *	output_stream)
545 {
546     HTStream * me;
547     if ((me = (HTStream *) HT_CALLOC(1, sizeof(* me))) == NULL)
548         HT_OUTOFMEM("HTMIMEConvert");
549     me->isa = &HTMIME;
550     me->request = request;
551     me->response = HTRequest_response(request);
552     me->net = HTRequest_net(request);
553     me->target = output_stream;
554     me->target_format = output_format;
555     me->save_stream = LocalSaveStream ? LocalSaveStream : HTBlackHoleConverter;
556     me->token = HTChunk_new(256);
557     me->value = HTChunk_new(256);
558     me->hash = 0;
559     me->EOLstate = EOL_BEGIN;
560     me->haveToken = NO;
561     return me;
562 }
563 
564 /*	MIME header ONLY parser stream
565 **	------------------------------
566 **	This stream parses a complete MIME header and then returnes HT_PAUSE.
567 **	It does not set up any streams and resting data stays in the buffer.
568 **	This can be used if you only want to parse the headers before you
569 **	decide what to do next. This is for example the case in a server app.
570 */
HTMIMEHeader(HTRequest * request,void * param,HTFormat input_format,HTFormat output_format,HTStream * output_stream)571 PUBLIC HTStream * HTMIMEHeader (HTRequest *	request,
572 				void *		param,
573 				HTFormat	input_format,
574 				HTFormat	output_format,
575 				HTStream *	output_stream)
576 {
577     HTStream * me = HTMIMEConvert(request, param, input_format,
578 				  output_format, output_stream);
579     me->mode |= HT_MIME_HEADER;
580     return me;
581 }
582 
HTMIMEContinue(HTRequest * request,void * param,HTFormat input_format,HTFormat output_format,HTStream * output_stream)583 PUBLIC HTStream * HTMIMEContinue (HTRequest *	request,
584 				  void *	param,
585 				  HTFormat	input_format,
586 				  HTFormat	output_format,
587 				  HTStream *	output_stream)
588 {
589     HTStream * me = HTMIMEConvert(request, param, input_format,
590 				  output_format, output_stream);
591     me->mode |= HT_MIME_CONT;
592     return me;
593 }
594 
HTMIMEUpgrade(HTRequest * request,void * param,HTFormat input_format,HTFormat output_format,HTStream * output_stream)595 PUBLIC HTStream * HTMIMEUpgrade  (HTRequest *	request,
596 				  void *	param,
597 				  HTFormat	input_format,
598 				  HTFormat	output_format,
599 				  HTStream *	output_stream)
600 {
601     HTStream * me = HTMIMEConvert(request, param, input_format,
602 				  output_format, output_stream);
603     me->mode |= HT_MIME_UPGRADE;
604     return me;
605 }
606 
607 /*	MIME footer ONLY parser stream
608 **	------------------------------
609 **	Parse only a footer, for example after a chunked encoding.
610 */
HTMIMEFooter(HTRequest * request,void * param,HTFormat input_format,HTFormat output_format,HTStream * output_stream)611 PUBLIC HTStream * HTMIMEFooter (HTRequest *	request,
612 				void *		param,
613 				HTFormat	input_format,
614 				HTFormat	output_format,
615 				HTStream *	output_stream)
616 {
617     HTStream * me = HTMIMEConvert(request, param, input_format,
618 				  output_format, output_stream);
619     me->mode |= HT_MIME_FOOTER;
620     return me;
621 }
622 
623 #ifndef NO_CACHE
624 /*
625 **	A small BEFORE filter that just finds a cache entry unconditionally
626 **	and loads the entry. All freshness and any other constraints are
627 **	ignored.
628 */
HTCacheLoadFilter(HTRequest * request,void * param,int mode)629 PRIVATE int HTCacheLoadFilter (HTRequest * request, void * param, int mode)
630 {
631     HTParentAnchor * anchor = HTRequest_anchor(request);
632     char * default_name;
633     HTCache * cache;
634 
635     default_name = HTRequest_defaultPutName (request);
636     cache = HTCache_find(anchor, default_name);
637 
638     HTTRACE(STREAM_TRACE, "Cache Load.. loading partial cache entry\n");
639     if (cache) {
640 	char * name = HTCache_name(cache);
641 	HTAnchor_setPhysical(anchor, name);
642 	HTCache_addHit(cache);
643 	HT_FREE(name);
644     }
645     return HT_OK;
646 }
647 
648 /*
649 **	A small AFTER filter that flushes the PIPE buffer so that we can
650 **	get the rest of the data
651 */
HTCacheFlushFilter(HTRequest * request,HTResponse * response,void * param,int mode)652 PRIVATE int HTCacheFlushFilter (HTRequest * request, HTResponse * response,
653 				void * param, int mode)
654 {
655     HTStream * pipe = (HTStream *) param;
656     if (pipe) {
657 	HTTRACE(STREAM_TRACE, "Cache Flush. Flushing PIPE buffer\n");
658 	(*pipe->isa->flush)(pipe);
659     }
660 
661     /*
662     **  We also delete the request obejct and stop more filters from being called.
663     **  As this is our own request, it's OK to do that
664     */
665     HTRequest_delete(request);
666     return HT_ERROR;
667 }
668 #endif
669 
670 /*	Partial Response MIME parser stream
671 **	-----------------------------------
672 **	In case we sent a Range conditional GET we may get back a partial
673 **	response. This response must be appended to the already existing
674 **	cache entry before presented to the user.
675 **	We do this by continuing to load the new object into a temporary
676 **	buffer and at the same time start the cache load of the already
677 **	existing object. When we have loaded the cache we merge the two
678 **	buffers.
679 */
HTMIMEPartial(HTRequest * request,void * param,HTFormat input_format,HTFormat output_format,HTStream * output_stream)680 PUBLIC HTStream * HTMIMEPartial (HTRequest *	request,
681 				 void *		param,
682 				 HTFormat	input_format,
683 				 HTFormat	output_format,
684 				 HTStream *	output_stream)
685 {
686 #ifndef NO_CACHE
687     HTParentAnchor * anchor = HTRequest_anchor(request);
688     HTFormat format = HTAnchor_format(anchor);
689     HTStream * pipe = NULL;
690 
691     /*
692     **  The merge stream is a place holder for where we can put data when it
693     **  arrives. We have two feeds: one from the cache and one from the net.
694     **  We call the stream stack already now to get the right output stream.
695     **  We can do this as we already know the content type from when we got the
696     **  first part of the object.
697     */
698     HTStream * merge = HTMerge(HTStreamStack(format,
699 					     output_format, output_stream,
700 					     request, YES), 2);
701 
702     /*
703     **  Now we create the MIME parser stream in partial data mode. We also
704     **  set the target to our merge stream.
705     */
706     HTStream * me = HTMIMEConvert(request, param, input_format,
707 				  output_format, output_stream);
708     me->mode |= HT_MIME_PARTIAL;
709     me->target = merge;
710 
711     /*
712     **  Create the pipe buffer stream to buffer the data that we read
713     **  from the network
714     */
715     if ((pipe = HTPipeBuffer(me->target, 0))) me->target = pipe;
716 
717     /*
718     **  Now start the second load from the cache. First we read this data from
719     **  the cache and then we flush the data that we have read from the net.
720     */
721     {
722 	HTRequest * cache_request = HTRequest_new();
723 
724 	/*
725 	**  Set the output format to source and the output stream to the
726 	**  merge stream. As we have already set up the stream pipe, we just
727 	**  load it as source.
728 	*/
729 	HTRequest_setOutputFormat(cache_request, WWW_SOURCE);
730 	HTRequest_setOutputStream(cache_request, merge);
731 
732 	/*
733 	**  Bind the anchor to the new request and also register a local
734 	**  AFTER filter to flush the pipe buffer so that we can get
735 	**  rest of the data through.
736 	*/
737 	HTRequest_setAnchor(cache_request, (HTAnchor *) anchor);
738 	HTRequest_addBefore(cache_request, HTCacheLoadFilter, NULL, NULL,
739 			    HT_FILTER_FIRST, YES);
740 	HTRequest_addAfter(cache_request, HTCacheFlushFilter, NULL, pipe,
741 			   HT_ALL, HT_FILTER_FIRST, YES);
742 
743 	HTTRACE(STREAM_TRACE, "Partial..... Starting cache load\n");
744 	HTLoad(cache_request, NO);
745     }
746     return me;
747 #else
748     return NULL;
749 #endif
750 }
751 
HTMIME_setSaveStream(HTConverter * save_stream)752 PUBLIC void HTMIME_setSaveStream (HTConverter * save_stream)
753 {
754     LocalSaveStream = save_stream;
755 }
756 
HTMIME_saveStream(void)757 PUBLIC HTConverter * HTMIME_saveStream (void)
758 {
759     return LocalSaveStream;
760 }
761 
762 #ifndef NO_CACHE
763 /* HTMIME_anchor2response
764  * Copies the anchor HTTP headers into a response object by means
765  * of the generic _dispatchParsers function. Written so that we can
766  * copy the HTTP headers stored in the cache to the response object.
767  */
HTMIME_anchor2response(HTRequest * req)768 PRIVATE void HTMIME_anchor2response (HTRequest * req)
769 {
770   char * token;
771   char * value;
772   HTAssocList * header;
773   HTAssoc * pres;
774   HTResponse * res;
775   HTParentAnchor * anchor;
776 
777   if (!req)
778     return;
779 
780   anchor = HTRequest_anchor (req);
781   header = HTAnchor_header (anchor);
782   if (!anchor || !header)
783     return;
784 
785   while ((pres = (HTAssoc *) HTAssocList_nextObject (header)))
786     {
787       token = HTAssoc_name (pres);
788       value = HTAssoc_value (pres);
789       _dispatchParsers (req, token, value);
790     }
791 
792   /*
793   **  Notify the response object not to delete the lists that we
794   **  have inherited from the anchor object
795   */
796   res = HTRequest_response (req);
797   HTResponse_isCached (res, YES);
798 }
799 
800 /*
801 **	A small AFTER filter that is a frontend to the
802 **      HTMIME_anchor2headers function.
803 */
804 
HTCacheCopyHeaders(HTRequest * request,void * param,HTFormat input_format,HTFormat output_format,HTStream * output_stream)805 PUBLIC HTStream * HTCacheCopyHeaders   (HTRequest *	request,
806 					void *		param,
807 					HTFormat	input_format,
808 					HTFormat	output_format,
809 					HTStream *	output_stream)
810 {
811     HTTRACE(STREAM_TRACE, "Cache Copy Headers.. Copying headers into the response object\n");
812     HTMIME_anchor2response (request);
813     return HT_OK;
814 }
815 #endif /* NO_CACHE */
816