Пример #1
0
/*		Find the cost of a filter stack
**		-------------------------------
**
**	Must return the cost of the same stack which StreamStack would set up.
**
** On entry,
**	length	The size of the data to be converted
*/
PUBLIC double HTStackValue (HTList *	theseConversions,
			    HTFormat	rep_in,
			    HTFormat	rep_out,
			    double	initial_value,
			    long int	length)
{
    int which_list;
    HTList* conversion[2];
    
    HTTRACE(CORE_TRACE, "StackValue.. Evaluating stream stack for %s worth %.3f to %s\n" _
	    HTAtom_name(rep_in) _ initial_value _ HTAtom_name(rep_out));

    if (rep_out == WWW_SOURCE || rep_out == rep_in) return 0.0;

    conversion[0] = theseConversions;
    conversion[1] = HTConversions;
    
    for(which_list = 0; which_list<2; which_list++)
     if (conversion[which_list]) {
        HTList * cur = conversion[which_list];
	HTPresentation * pres;
	while ((pres = (HTPresentation*)HTList_nextObject(cur))) {
	    if (pres->rep == rep_in &&
		(pres->rep_out == rep_out || HTMIMEMatch(pres->rep_out, rep_out))) {
	        double value = initial_value * pres->quality;
		if (HTMaxSecs != 0.0)
		    value = value - (length*pres->secs_per_byte + pres->secs)
			                 /HTMaxSecs;
		return value;
	    }
	}
    }
    return NO_VALUE_FOUND;		/* Really bad */
}
Пример #2
0
/*	Create a new coder and insert it into stream chain
**	--------------------------------------------------
**	Creating the content decoding stack is not based on quality factors as
**	we don't have the freedom as with content types. Specify whether you
**	you want encoding or decoding using the BOOL "encode" flag.
*/
PUBLIC HTStream * HTContentCodingStack (HTEncoding	encoding,
					HTStream *	target,
					HTRequest *	request,
					void *		param,
					BOOL		encode)
{
    HTList * coders[2];
    HTStream * top = target;
    HTCoding * pres = NULL;
    HTCoding * best_match = NULL;
    double best_quality = -1e30;		/* Pretty bad! */
    int cnt;
    if (!encoding || !request) {
	HTTRACE(CORE_TRACE, "Codings... Nothing applied...\n");
	return target ? target : HTErrorStream();
    }
    coders[0] = HTRequest_encoding(request);
    coders[1] = HTContentCoders;
    HTTRACE(CORE_TRACE, "C-E......... Looking for `%s\'\n" _ HTAtom_name(encoding));
    for (cnt=0; cnt < 2; cnt++) {
	HTList * cur = coders[cnt];
	while ((pres = (HTCoding *) HTList_nextObject(cur))) {
	    if ((pres->encoding == encoding || HTMIMEMatch(pres->encoding, encoding)) &&
		pres->quality > best_quality) {
		best_match = pres;
		best_quality = pres->quality;
	    }
	}
    }

    if (best_match) {
	HTTRACE(CORE_TRACE, "C-E......... Found `%s\'\n" _ HTAtom_name(best_match->encoding));
	if (encode) {
	    if (best_match->encoder)
		top = (*best_match->encoder)(request, param, encoding, top);
	} else {
	    if (best_match->decoder)
		top = (*best_match->decoder)(request, param, encoding, top);
	}
    } else if (!HTFormat_isUnityContent(encoding)) {

	/*
	**  If this is not a unity coding and we didn't find any coders
	**  that could handle it then put in a local file save stream
	**  instead of the stream that we got.
	*/
	if (encode) {
	    HTTRACE(CORE_TRACE, "C-E......... NOT FOUND - can't encode stream!\n");
	} else {
	    HTTRACE(CORE_TRACE, "C-E......... NOT FOUND - error!\n");
	    top = HTBlackHole();
	}
    }
    return top;
}
Пример #3
0
PRIVATE double type_value (HTAtom * content_type, HTList * accepted)
{
    if (!content_type) return (1.0);
    if (accepted) {
	HTList * cur = accepted;
	HTPresentation * pres;
	HTPresentation * wild = NULL;
	while ((pres = (HTPresentation *) HTList_nextObject(cur))) {
	    if (pres->rep == content_type)
		return pres->quality;
	    else if (HTMIMEMatch(pres->rep, content_type))
		wild = pres;
	}
	if (wild) return wild->quality;
	else return (0.0);				  /* Nothing matched */
    }
    return (1.0);				      /* We accept all types */
}
Пример #4
0
PRIVATE double encoding_value (HTAtom * encoding, HTList * accepted)
{
    if (!encoding) return (1.0);
    if (accepted) {
	HTList * cur = accepted;
	HTAcceptNode * node;
	HTAcceptNode * wild = NULL;
	const char * e = HTAtom_name(encoding);
	if (!strcmp(e, "7bit") || !strcmp(e, "8bit") || !strcmp(e, "binary"))
	    return (1.0);
	while ((node = (HTAcceptNode*)HTList_nextObject(cur))) {
	    if (node->atom == encoding)
		return node->quality;
	    else if (HTMIMEMatch(node->atom, encoding))
		wild = node;
	}
	if (wild) return wild->quality;
	else return (0.0);				  /* Nothing matched */
    }
    return (1.0);				  /* We accept all encodings */
}
Пример #5
0
/*	HTTPMakeRequest
**	---------------
**	Makes a HTTP/1.0-1.1 request header.
*/
PRIVATE int HTTPMakeRequest (HTStream * me, HTRequest * request)
{
    HTMethod method = HTRequest_method(request);
    HTRqHd request_mask = HTRequest_rqHd(request);
    HTParentAnchor * anchor = HTRequest_anchor(request);
    char * etag = HTAnchor_etag(anchor);
    char crlf[3];
    char qstr[10];
    *crlf = CR; *(crlf+1) = LF; *(crlf+2) = '\0';

    /* Generate the HTTP/1.x RequestLine */
    if (me->state == 0) {
	if (method != METHOD_INVALID) {
	    PUTS(HTMethod_name(method));
	    PUTC(' ');
	} else
	    PUTS("GET ");
	me->state++;
    }

    /*
    **  Generate the Request URI. If we are using full request URI then it's
    **  easy. Otherwise we must filter out the path part of the URI.
    **  In case it's a OPTIONS request then if there is no pathinfo then use
    **  a * instead. If we use a method different from GET or HEAD then use
    **  the content-location if available.
    */
    if (me->state == 1) {
	char * abs_location = NULL;
	char * addr = HTAnchor_physical(anchor);
	char * location;

        /* JK: If the application specified a content-location (which is
           stored in the request in default put-name!), we use it instead
           of the URL that's being saved to. This is like having a user
           defined Content-Location */
        location = HTRequest_defaultPutName (request);
        if (location)
	  {
	    if (HTURL_isAbsolute (location))
	      {
		char * relative;
		relative = HTRelative (location, location);
		abs_location = HTParse (relative + 2, addr, PARSE_ALL);
		HT_FREE (relative);
	      }
	    else
	      abs_location = HTParse (location, addr, PARSE_ALL);
	    addr = abs_location;
	  }

#if 0
	/*
	**  We don't use the content-location any more as it is superseeded
	**  by etags and the combination of the two might do more harm than
	**  good (The etag is not guaranteed to be unique over multiple URIs)
	*/

	/*
	**  If we are using a method different from HEAD and GET then use
	**  the Content-Location if available, else the Request-URI.
	*/
	if (!HTMethod_isSafe(method)) {
	    char * location = HTAnchor_location(anchor);
	    if (location) {
		if (HTURL_isAbsolute(location))
		    addr = location;
		else {
		    /*
		    **  We have a content-location but it is relative and
		    **  must expand it either to the content-base or to
		    **  the Request-URI itself.
		    */
		    char * base = HTAnchor_base(anchor);
		    abs_location = HTParse(location, base, PARSE_ALL);
		    addr = abs_location;
		}
	    }
	}
#endif

	/*
	**  If we are using a proxy or newer versions of HTTP then we can
	**  send the full URL. Otherwise we only send the path.
	*/
	if (HTRequest_fullURI(request))
	    StrAllocCopy(me->url, addr);
	else {
	    me->url = HTParse(addr, "", PARSE_PATH | PARSE_PUNCTUATION);
	    if (method == METHOD_OPTIONS) {
		/*
		** We don't preserve the final slash or lack of same through
		** out the code. This is mainly for optimization reasons
		** but it gives a problem OPTIONS. We can either send a "*"
		** or a "/" but not both. For now we send a "*".
		*/
		if (!strcmp(me->url, "/")) *me->url = '*';
	    }
	}
	HT_FREE(abs_location);
	me->state++;
    }

    /*
    **  Now send the URL that we have put together
    */
    if (me->state == 2) {
	int status = HT_OK;
	if ((status = PUTS(me->url)) != HT_OK) return status;
	me->state++;

#if 0
	fprintf(stderr, "Requesting '%s'\n", me->url);
#endif

    }
    PUTC(' ');

    /*
    **  Send out the version number. If we know it is a HTTP/1.0 server we
    **  are talking to then use HTTP/1.0, else use HTTP/1.1 as default version
    **  number
    */
    if (me->version == HTTP_10)
	PUTS(HTTP_VERSION_10);
    else
	PUTS(HTTP_VERSION);
    PUTBLOCK(crlf, 2);

    /* Request Headers */
    if (request_mask & HT_C_ACCEPT_TYPE) {
	HTFormat format = HTRequest_outputFormat(request);
	
	/*
	** If caller has specified a specific output format then use this.
	** Otherwise use all the registered converters to generate the 
	** accept header
	*/
	if (format == WWW_PRESENT) {
	    int list;
	    HTList *cur;
	    BOOL first=YES;
	    for (list=0; list<2; list++) {
		if ((!list && ((cur = HTFormat_conversion()) != NULL)) ||
		    (list && ((cur = HTRequest_conversion(request))!=NULL))) {
		    HTPresentation * pres;
		    while ((pres=(HTPresentation *) HTList_nextObject(cur))) {
			if (pres->rep_out==WWW_PRESENT && pres->quality<=1.0) {
			    if (first) {
				PUTS("Accept: ");
				first=NO;
			    } else
				PUTC(',');
			    PUTS(HTAtom_name(pres->rep));
			    if (pres->quality < 1.0 && pres->quality >= 0.0) {
				sprintf(qstr, ";q=%1.1f", pres->quality);
				PUTS(qstr);
			    }
			}
		    }
		}
	    }
	    if (!first) PUTBLOCK(crlf, 2);
	} else {

	    /*
	    **  If we have an explicit output format then only send
	    **  this one if not this is an internal libwww format
	    **	of type www/<star>
	    */
	    if (!HTMIMEMatch(WWW_INTERNAL, format)) {
		PUTS("Accept: ");
		PUTS(HTAtom_name(format));
		PUTBLOCK(crlf, 2);
	    }
	}	
    }
    if (request_mask & HT_C_ACCEPT_CHAR) {
	int list;
	HTList *cur;
	BOOL first=YES;
	for (list=0; list<2; list++) {
	    if ((!list && ((cur = HTFormat_charset()) != NULL)) ||
		(list && ((cur = HTRequest_charset(request)) != NULL))) {
		HTAcceptNode *pres;
		while ((pres = (HTAcceptNode *) HTList_nextObject(cur))) {
		    if (first) {
			PUTS("Accept-Charset: ");
			first=NO;
		    } else
			PUTC(',');
		    PUTS(HTAtom_name(pres->atom));
		    if (pres->quality < 1.0 && pres->quality >= 0.0) {
			sprintf(qstr, ";q=%1.1f", pres->quality);
			PUTS(qstr);
		    }
		}
	    }
	}
	if (!first) PUTBLOCK(crlf, 2);
    }
    if (request_mask & HT_C_ACCEPT_ENC) {
	int list;
	HTList *cur;
	BOOL first=YES;
	for (list=0; list<2; list++) {
	    if ((!list && ((cur = HTFormat_contentCoding()) != NULL)) ||
		(list && ((cur = HTRequest_encoding(request)) != NULL))) {
		HTCoding * pres;
		while ((pres = (HTCoding *) HTList_nextObject(cur))) {
		    double quality = HTCoding_quality(pres);
		    if (first) {
			PUTS("Accept-Encoding: ");
			first = NO;
		    } else
			PUTC(',');
		    PUTS(HTCoding_name(pres));
		    if (quality < 1.0 && quality >= 0.0) {
			sprintf(qstr, ";q=%1.1f", quality);
			PUTS(qstr);
		    }
		}
	    }
	}
	if (!first) PUTBLOCK(crlf, 2);
    }
    if (request_mask & HT_C_ACCEPT_TE) {
	int list;
	HTList *cur;
	BOOL first=YES;
	for (list=0; list<2; list++) {
	    if ((!list && ((cur = HTFormat_transferCoding()) != NULL)) ||
		(list && ((cur = HTRequest_transfer(request)) != NULL))) {
		HTCoding * pres;
		while ((pres = (HTCoding *) HTList_nextObject(cur))) {
		    double quality = HTCoding_quality(pres);
		    const char * coding = HTCoding_name(pres);
		    if (first) {
			PUTS("TE: ");
			first = NO;
		    } else
			PUTC(',');

		    /* Special check for "chunked" which is translated to "trailers" */
		    if (!strcasecomp(coding, "chunked"))
			PUTS("trailers");
		    else
			PUTS(coding);
		    if (quality < 1.0 && quality >= 0.0) {
			sprintf(qstr, ";q=%1.1f", quality);
			PUTS(qstr);
		    }
		}
	    }
	}
	if (!first) PUTBLOCK(crlf, 2);
    }
    if (request_mask & HT_C_ACCEPT_LAN) {
	int list;
	HTList *cur;
	BOOL first=YES;
	for (list=0; list<2; list++) {
	    if ((!list && ((cur = HTFormat_language()) != NULL)) ||
		(list && ((cur = HTRequest_language(request)) != NULL))) {
		HTAcceptNode *pres;
		while ((pres = (HTAcceptNode *) HTList_nextObject(cur))) {
		    if (first) {
			PUTS("Accept-Language: ");
			first=NO;
		    } else
			PUTC(',');
		    PUTS(HTAtom_name(pres->atom));
		    if (pres->quality < 1.0 && pres->quality >= 0.0) {
			sprintf(qstr, ";q=%1.1f", pres->quality);
			PUTS(qstr);
		    }
		}
	    }
	}
	if (!first) PUTBLOCK(crlf, 2);
    }
    if (request_mask & HT_C_AUTH) {
	HTAssocList * cur = HTRequest_credentials(request);
	if (cur) {				    /* Access authentication */
	    HTAssoc * pres;
	    while ((pres = (HTAssoc *) HTAssocList_nextObject(cur))) {
		PUTS(HTAssoc_name(pres));
		PUTS(": ");
		PUTS(HTAssoc_value(pres));
		PUTBLOCK(crlf, 2);
	    }
	}
    }
    if (request_mask & HT_C_EXPECT) {
	HTAssocList * cur = HTRequest_expect(request);
	if (cur) {
	    BOOL first=YES;
	    HTAssoc * pres;
	    while ((pres = (HTAssoc *) HTAssocList_nextObject(cur))) {
		char * value = HTAssoc_value(pres);
		if (first) {
		    PUTS("Expect: ");
		    first = NO;
		} else
		    PUTC(',');

		/* Output the name */
		PUTS(HTAssoc_name(pres));

		/* Only output the value if not empty string */
		if (*value) {
		    PUTS("=");
		    PUTS(value);
		}
	    }
	    PUTBLOCK(crlf, 2);
	}
    }
    if (request_mask & HT_C_FROM) {
	HTUserProfile * up = HTRequest_userProfile(request);
	const char * mailaddress = HTUserProfile_email(up);
	if (mailaddress) {
	    PUTS("From: ");
	    PUTS(mailaddress);
	    PUTBLOCK(crlf, 2);
	}
    }
    if (request_mask & HT_C_HOST) {
	char *orig = HTAnchor_address((HTAnchor *) anchor);
	char *host = HTParse(orig, "", PARSE_HOST);
	char hostace[256];
#if 0
	/* Keep the port number for HTTP/1.1 compliance */
	char *ptr = strchr(host, ':');		     /* Chop off port number */
	if (ptr) *ptr = '\0';
#endif
        PUTS("Host: ");
    /****** still have to check UTF8toACE with port number */
    if (!HTACEfromUTF8 (host, hostace, 255)) {
	    PUTS(hostace);
	}
	else {
	    PUTS(host); /* this may be dangerous, but helps server side debugging */
        HTTRACE(PROT_TRACE, "HTTP........ Error: Cannot convert to ACE: `%s\'\n" _ host);
	}
	PUTBLOCK(crlf, 2);
	HT_FREE(orig);
	HT_FREE(host);
    }

    /*
    **  In the "If-*" series of headers, the ones related to etags have higher
    **  priority than the date relates ones. That is, if we have a etag then
    **  use that, otherwise use the date. First we check for range, match, and
    **  unmodified-since.
    */
    if (request_mask & HT_C_IF_RANGE && etag) {
	PUTS("If-Range: \"");
	PUTS(etag);
	PUTC('"');
	PUTBLOCK(crlf, 2);
	HTTRACE(PROT_TRACE, "HTTP........ If-Range using etag `%s\'\n" _ etag);
    } else if (request_mask & HT_C_IF_MATCH_ANY) {
	PUTS("If-Match: *");
	PUTBLOCK(crlf, 2);
	HTTRACE(PROT_TRACE, "HTTP........ If-Match using `*\'\n");
    } else if (request_mask & HT_C_IF_MATCH && etag) {
	PUTS("If-Match: \"");
	PUTS(etag);
	PUTC('"');
	PUTBLOCK(crlf, 2);
	HTTRACE(PROT_TRACE, "HTTP........ If-Match using etag `%s\'\n" _ etag);
    } else if (request_mask & HT_C_IF_UNMOD_SINCE) {
	time_t lm = HTAnchor_lastModified(anchor);
	if (lm > 0) {
	    PUTS("If-Unmodified-Since: ");
	    PUTS(HTDateTimeStr(&lm, NO));
	    PUTBLOCK(crlf, 2);
	    HTTRACE(PROT_TRACE, "HTTP........ If-Unmodified-Since `%s\'\n" _ HTDateTimeStr(&lm, NO));
	}
    }

    /*
    **  If-None-Match and If-Modified-Since are equivalent except that the
    **  first uses etags and the second uses dates. Etags have precedence over
    **  dates.
    */
    if (request_mask & HT_C_IF_NONE_MATCH_ANY) {
	PUTS("If-None-Match: *");
	PUTBLOCK(crlf, 2);
	HTTRACE(PROT_TRACE, "HTTP........ If-None-Match using `*\'\n");
    } else if (request_mask & HT_C_IF_NONE_MATCH && etag) {
	PUTS("If-None-Match: \"");
	PUTS(etag);
	PUTC('"');
	PUTBLOCK(crlf, 2);
	HTTRACE(PROT_TRACE, "HTTP........ If-None-Match `%s\'\n" _ etag);
    }
    if (request_mask & HT_C_IMS) {
	time_t lm = HTAnchor_lastModified(anchor);
	if (lm > 0) {
	    PUTS("If-Modified-Since: ");
	    PUTS(HTDateTimeStr(&lm, NO));
	    PUTBLOCK(crlf, 2);
	    HTTRACE(PROT_TRACE, "HTTP........ If-Modified-Since `%s\'\n" _ HTDateTimeStr(&lm, NO));
	}
    }

    /*
    **  Max forwards is mainly for TRACE where we want to be able to stop the
    **  TRACE at a specific location un the message path.
    */
    if (request_mask & HT_C_MAX_FORWARDS) {
	int hops = HTRequest_maxForwards(request);
	if (hops >= 0) {
	    sprintf(qstr, "%d", hops);
	    PUTS("Max-Forwards: ");
	    PUTS(qstr);
	    PUTBLOCK(crlf, 2);
	}
    }

    /*
    **  Range requests. For now, we only take the first entry registered for
    **  this request. This means that you can only send a single "unit" and
    **  then a set of range within this unit. This is in accordance with 
    **  HTTP/1.1. Multiple units will go on multiple lines.
    */
    if (request_mask & HT_C_RANGE) {
	HTAssocList * cur = HTRequest_range(request);
	if (cur) {				    	   /* Range requests */
	    HTAssoc * pres;
	    while ((pres = (HTAssoc *) HTAssocList_nextObject(cur))) {
		PUTS("Range: ");
		PUTS(HTAssoc_name(pres));			     /* Unit */
		PUTS("=");
		PUTS(HTAssoc_value(pres));	  /* Ranges within this unit */
		PUTBLOCK(crlf, 2);
	    }
	}
    }
    if (request_mask & HT_C_REFERER) {
	HTParentAnchor * parent_anchor = HTRequest_parent(request);
	if (parent_anchor) {
	    char * act = HTAnchor_address((HTAnchor *) anchor);
	    char * parent = HTAnchor_address((HTAnchor *) parent_anchor);
#if 1
	    char * relative = HTRelative(parent, act);
#else
	    char * relative = HTParse(parent, act,
				      PARSE_ACCESS|PARSE_HOST|PARSE_PATH|PARSE_PUNCTUATION);
#endif
	    if (relative && *relative) {
		PUTS("Referer: ");
		PUTS(relative);
		PUTBLOCK(crlf, 2);
	    }
	    HT_FREE(act);
	    HT_FREE(parent);
	    HT_FREE(relative);
	}
    }
    if (request_mask & HT_C_USER_AGENT) {
	PUTS("User-Agent: ");
	PUTS(HTLib_appName());
	PUTC('/');
	PUTS(HTLib_appVersion());
	PUTC(' ');
	PUTS(HTLib_name());
	PUTC('/');
	PUTS(HTLib_version());
	PUTBLOCK(crlf, 2);
    }
    HTTRACE(PROT_TRACE, "HTTP........ Generating HTTP/1.x Request Headers\n");
    return HT_OK;
}
Пример #6
0
/*	Create a Content Type filter stack
**	----------------------------------
**	If a wildcard match is made, a temporary HTPresentation
**	structure is made to hold the destination format while the
**	new stack is generated. This is just to pass the out format to
**	MIME so far.  Storing the format of a stream in the stream might
**	be a lot neater.
**
**	The star/star format is special, in that if you can take
**	that you can take anything.
*/
PUBLIC HTStream * HTStreamStack (HTFormat	rep_in,
				 HTFormat	rep_out,
				 HTStream *	output_stream,
				 HTRequest *	request,
				 BOOL		guess)
{
    HTList * conversion[2];
    int which_list;
    double best_quality = -1e30;		/* Pretty bad! */
    HTPresentation *pres, *best_match=NULL;
    if (rep_out == WWW_RAW) {
	HTTRACE(CORE_TRACE, "StreamStack. Raw output...\n");
	return output_stream ? output_stream : HTErrorStream();
    }

    if (rep_out == rep_in) {
	HTTRACE(CORE_TRACE, "StreamStack. Identical input/output format (%s)\n" _ 
		     HTAtom_name(rep_out));
	return output_stream ? output_stream : HTErrorStream();
    }

#ifdef HTDEBUG
    if (CORE_TRACE) {
	const char *p = HTAtom_name(rep_in);
	const char *q = HTAtom_name(rep_out); 
	HTTRACE(CORE_TRACE, "StreamStack. Constructing stream stack for %s to %s\n" _
		p ? p : "<NULL>" _ q ? q : "<NULL>");
    }
#endif /* HTDEBUG */

    conversion[0] = HTRequest_conversion(request);
    conversion[1] = HTConversions;

    for(which_list = 0; which_list<2; which_list++) {
	HTList * cur = conversion[which_list];
	while ((pres = (HTPresentation*)HTList_nextObject(cur))) {
	    if ((pres->rep==rep_in || HTMIMEMatch(pres->rep, rep_in)) &&
		(pres->rep_out==rep_out || HTMIMEMatch(pres->rep_out,rep_out))){
		if (!best_match || better_match(pres->rep, best_match->rep) ||
		    (!better_match(best_match->rep, pres->rep) &&
		     pres->quality > best_quality)) {
#ifdef HAVE_SYSTEM
		    int result=0;
		    if (pres->test_command) {
			result = system(pres->test_command);
			HTTRACE(CORE_TRACE, "StreamStack. system(%s) returns %d\n" _ pres->test_command _ result);
		    }
		    if (!result) {
			best_match = pres;
			best_quality = pres->quality;
		    }
#else
		    best_match = pres;
		    best_quality = pres->quality;
#endif /* HAVE_SYSTEM */
		}
	    }
	}
    }

    if (best_match) {
 	if (rep_out == WWW_SOURCE && best_match->rep_out != WWW_SOURCE) {
	    HTTRACE(CORE_TRACE, "StreamStack. Source output\n");
	    return output_stream ? output_stream : HTErrorStream();
	}
	return (*best_match->converter)(request, best_match->command,
					rep_in, rep_out, output_stream);
    }
    if (rep_out == WWW_SOURCE) {
	HTTRACE(CORE_TRACE, "StreamStack. Source output\n");
	return output_stream ? output_stream : HTErrorStream();
    }

    HTTRACE(CORE_TRACE, "StreamStack. NOT FOUND - error!\n");
    return HTBlackHole();
}