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