/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */


/*
 * return.c --
 *
 *	Functions that return data to a browser. 
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/return.c,v 1.33.2.6 2004/07/19 02:47:10 dossy Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

#define MAX_RECURSION 3       /* Max return direct recursion limit. */

#define HTTP11_HDR_TE "Transfer-Encoding"
#define HTTP11_TE_CHUNKED "chunked"


/*
 * Local functions defined in this file
 */

static int ReturnRedirect(Ns_Conn *conn, int status, int *resultPtr);
static int ReturnOpen(Ns_Conn *conn, int status, char *type, Tcl_Channel chan,
		      FILE *fp, int fd, int len);
static int ReturnCharData(Ns_Conn *conn, int status, char *data, int len,
                          char *type, int sendRaw);

/*
 * This structure connections HTTP response codes to their descriptions.
 */

static struct {
    int	  status;
    char *reason;
} reasons[] = {
    {100, "Continue"},
    {101, "Switching Protocols"},
    {102, "Processing"},
    {200, "OK"},
    {201, "Created"},
    {202, "Accepted"},
    {203, "Non-Authoritative Information"},
    {204, "No Content"},
    {205, "Reset Content"},
    {206, "Partial Content"},
    {207, "Multi-Status"},
    {300, "Multiple Choices"},
    {301, "Moved"},
    {302, "Found"},
    {303, "See Other"},
    {304, "Not Modified"},
    {305, "Use Proxy"},
    {307, "Temporary Redirect"},
    {400, "Bad Request"},
    {401, "Unauthorized"},
    {402, "Payment Required"},
    {403, "Forbidden"},
    {404, "Not Found"},
    {405, "Method Not Allowed"},
    {406, "Not Acceptable"},
    {407, "Proxy Authentication Required"},
    {408, "Request Timeout"},
    {409, "Conflict"},
    {410, "Gone"},
    {411, "Length Required"},
    {412, "Precondition Failed"},
    {413, "Request Entity Too Large"},
    {414, "Request-URI Too Long"},
    {415, "Unsupported Media Type"},
    {416, "Requested Range Not Satisfiable"},
    {417, "Expectation Failed"},
    {422, "Unprocessable Entity"},
    {423, "Locked"},
    {424, "Method Failure"},
    {425, "Insufficient Space On Resource"},
    {500, "Internal Server Error"},
    {501, "Not Implemented"},
    {502, "Bad Gateway"},
    {503, "Service Unavailable"},
    {504, "Gateway Timeout"},
    {505, "HTTP Version Not Supported"},
    {507, "Insufficient Storage"}
};

/*
 * Static variables defined in this file.
 */

static int             nreasons = (sizeof(reasons) / sizeof(reasons[0]));


/*
 *----------------------------------------------------------------------
 *
 * Ns_RegisterReturn --
 *
 *	Associate a URL with a status. Rather than return the
 *	default error page for this status, a redirect will be
 *	issued to the url.
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_RegisterReturn(int status, char *url)
{
    NsServer	  *servPtr;
    Tcl_HashEntry *hPtr;
    int            new;

    servPtr = NsGetInitServer();
    if (servPtr != NULL) {
    	hPtr = Tcl_CreateHashEntry(&servPtr->request.redirect,
				   (char *) status, &new);
    	if (!new) {
	    ns_free(Tcl_GetHashValue(hPtr));
    	}
    	if (url == NULL) {
	    Tcl_DeleteHashEntry(hPtr);
    	} else {
	    Tcl_SetHashValue(hPtr, ns_strdup(url));
	}
    }
}

static int
IsSetupForChunkedEncoding(Ns_Conn *conn) 
{
    int headerCount = 0;
    int i;
    Ns_Set* outHeaders;

    if (conn == NULL)
        return 0;

    outHeaders = Ns_ConnOutputHeaders(conn);
    headerCount = Ns_SetSize (outHeaders);

    if (outHeaders && headerCount) {
        for (i = 0 ; i < headerCount ; i++) {
           if ( !strcasecmp(Ns_SetKey (outHeaders, i), HTTP11_HDR_TE) &&
	        !strcasecmp(Ns_SetValue (outHeaders, i), HTTP11_TE_CHUNKED) )
	       return 1;
           }
    }
    return 0;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnConstructHeaders --
 *
 *	Put the header of an HTTP response into the dstring. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Content length and connection-keepalive headers will be added 
 *	if possible. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_ConnConstructHeaders(Ns_Conn *conn, Ns_DString *dsPtr)
{
    int   i, length;
    char *reason;
    char  buf[100];
    char *value, *keep;
    char *key, *lengthHdr;
    Conn *connPtr;
    int  doChunkEncoding = 0;

    /*
     * Construct the HTTP response status line.
     */

    connPtr = (Conn *) conn;
    sprintf(buf, "%d", connPtr->responseStatus);
    reason = "Unknown Reason";
    for (i = 0; i < nreasons; i++) {
	if (reasons[i].status == connPtr->responseStatus) {
	    reason = reasons[i].reason;
	    break;
	}
    }

    doChunkEncoding = IsSetupForChunkedEncoding(conn);

    if (!doChunkEncoding)
        Ns_DStringVarAppend(dsPtr, "HTTP/1.0 ", buf, " ", reason, "\r\n", NULL);
    else
        Ns_DStringVarAppend(dsPtr, "HTTP/1.1 ", buf, " ", reason, "\r\n", NULL);

    /*
     * Output any headers.
     */

    if (conn->outputheaders != NULL) {
	/*
	 * Update the response length value directly from the
	 * header to be sent, i.e., don't trust programmers
	 * correctly called Ns_ConnSetLengthHeader().
	 */

	length = connPtr->responseLength;
	lengthHdr = Ns_SetIGet(conn->outputheaders, "content-length");
	if (lengthHdr != NULL) {
	    connPtr->responseLength = atoi(lengthHdr);
	}
	
	/*
	 * Output a connection keep-alive header only on
	 * any HTTP status 200 response which included
	 * a valid and correctly set content-length header.
	 */

	if (nsconf.keepalive.enabled &&
	    connPtr->headers != NULL &&
	    connPtr->request != NULL &&
	    (( (connPtr->responseStatus >= 200 && connPtr->responseStatus < 300) &&
	    (lengthHdr != NULL &&
	    connPtr->responseLength == length) || doChunkEncoding) ||
	    (connPtr->responseStatus == 304 || connPtr->responseStatus == 201 || connPtr->responseStatus == 207) ) &&
	    (nsconf.keepalive.allmethods == NS_TRUE ||
	    STREQ(connPtr->request->method, "GET")) &&
	    (key = Ns_SetIGet(conn->headers, "connection")) != NULL &&
	    STRIEQ(key, "keep-alive")) {
	    conn->flags |= NS_CONN_KEEPALIVE;
	    keep = "keep-alive";
	} else {
	    keep = "close";
	}

	Ns_ConnCondSetHeaders(conn, "Connection", keep);

	for (i = 0; i < Ns_SetSize(conn->outputheaders); i++) {
	    key = Ns_SetKey(conn->outputheaders, i);
	    value = Ns_SetValue(conn->outputheaders, i);
	    if (key != NULL && value != NULL) {
		Ns_DStringAppend(dsPtr, key);
		Ns_DStringNAppend(dsPtr, ": ", 2);
		Ns_DStringAppend(dsPtr, value);
		Ns_DStringNAppend(dsPtr, "\r\n", 2);
	    }
	}
    }
    Ns_DStringNAppend(dsPtr, "\r\n", 2);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnQueueHeaders --
 *
 *	Format basic headers to be sent on the connection.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See Ns_ConnConstructHeaders. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_ConnQueueHeaders(Ns_Conn *conn, int status)
{
    Conn *connPtr = (Conn *) conn;

    if (!(conn->flags & NS_CONN_SENTHDRS)) {
    	connPtr->responseStatus = status;
    	if (!(conn->flags & NS_CONN_SKIPHDRS)) {
	    Ns_ConnConstructHeaders(conn, &connPtr->queued);
	    connPtr->nContentSent -= connPtr->queued.length;
    	}
    	conn->flags |= NS_CONN_SENTHDRS;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnFlushHeaders --
 *
 *	Send out a well-formed set of HTTP headers with the given 
 *	status. 
 *
 * Results:
 *	Number of bytes written. 
 *
 * Side effects:
 *	See Ns_ConnQueueHeaders. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnFlushHeaders(Ns_Conn *conn, int status)
{
    Ns_ConnQueueHeaders(conn, status);
    return Ns_WriteConn(conn, NULL, 0);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnSetHeaders --
 *
 *	Add an output header. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_ConnSetHeaders(Ns_Conn *conn, char *field, char *value)
{
    Ns_SetPut(conn->outputheaders, field, value);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnCondSetHeaders --
 *
 *	Add an output header, only if it doesn't already exist. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_ConnCondSetHeaders(Ns_Conn *conn, char *field, char *value)
{
    if (Ns_SetIGet(conn->outputheaders, field) == NULL) {
        Ns_SetPut(conn->outputheaders, field, value);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReplaceHeaders --
 *
 *	Free the existing outpheaders and set them to a copy of 
 *	newheaders. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_ConnReplaceHeaders(Ns_Conn *conn, Ns_Set *newheaders)
{
    Ns_SetFree(conn->outputheaders);
    conn->outputheaders = Ns_SetCopy(newheaders);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnSetRequiredHeaders --
 *
 *	Set a sane set of minimal headers for any response:
 *	MIME-Version, Date, Server, Content-Type, Content-Length
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_ConnSetRequiredHeaders(Ns_Conn *conn, char *type, int length)
{
    Conn *connPtr = (Conn *) conn;
    Ns_DString ds;

    /*
     * Set the standard mime and date headers.
     */

    Ns_DStringInit(&ds);
    Ns_ConnCondSetHeaders(conn, "MIME-Version", "1.0");
    Ns_ConnCondSetHeaders(conn, "Date", Ns_HttpTime(&ds, NULL));
    Ns_DStringTrunc(&ds, 0);

    /*
     * Set the standard server header, prepending "NaviServer/2.0"
     * if AOLpress support is enabled.
     */

    if (connPtr->servPtr->opts.aolpress) {
    	Ns_DStringAppend(&ds, "NaviServer/2.0 ");
    }
    Ns_DStringVarAppend(&ds, Ns_InfoServerName(), "/", Ns_InfoServerVersion(), NULL);
    Ns_ConnCondSetHeaders(conn, "Server", ds.string);

    /*
     * Set the type and/or length headers if provided.  Note
     * that a valid length is required for connection keep-alive.
     */

    if (type != NULL) {
    	Ns_ConnSetTypeHeader(conn, type);
    }
    if (length >= 0) {
	Ns_ConnSetLengthHeader(conn, length);
    }

    Ns_DStringFree(&ds);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnSetTypeHeader --
 *
 *	Sets the Content-Type HTTP output header 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_ConnSetTypeHeader(Ns_Conn *conn, char *type)
{
    Ns_ConnSetHeaders(conn, "Content-Type", type);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnSetLengthHeader --
 *
 *	Set the Content-Length output header. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_ConnSetLengthHeader(Ns_Conn *conn, int length)
{
    char  buf[100];
    Conn *connPtr;

    connPtr = (Conn *) conn;
    connPtr->responseLength = length;
    sprintf(buf, "%d", length);
    Ns_ConnSetHeaders(conn, "Content-Length", buf);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnSetLastModifiedHeader --
 *
 *	Set the Last-Modified output header if it isn't already set. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_ConnSetLastModifiedHeader(Ns_Conn *conn, time_t *mtime)
{
    Ns_DString ds;

    Ns_DStringInit(&ds);
    Ns_ConnCondSetHeaders(conn, "Last-Modified", Ns_HttpTime(&ds, mtime));
    Ns_DStringFree(&ds);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnSetExpiresHeader --
 *
 *	Set the Expires output header. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_ConnSetExpiresHeader(Ns_Conn *conn, char *expires)
{
    Ns_ConnSetHeaders(conn, "Expires", expires);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnPrintfHeader --
 *
 *	Write a printf-style string right to the conn. 
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	Will write to the conn; you're expected to format this like a 
 *	header (like "foo: bar\n"). 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnPrintfHeader(Ns_Conn *conn, char *fmt,...)
{
    int result;
    Ns_DString ds;
    va_list ap;

    if (conn->request == NULL || conn->request->version < 1.0) {
	return NS_OK;
    }
    Ns_DStringInit(&ds);
    va_start(ap, fmt);
    Ns_DStringVPrintf(&ds, fmt, ap);
    va_end(ap);
    result = Ns_ConnSendDString(conn, &ds);
    Ns_DStringFree(&ds);
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnResetReturn --
 *
 *	Deprecated 
 *
 * Results:
 *	NS_OK 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnResetReturn(Ns_Conn *conn)
{
    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnAdminNotice --
 *
 *	Return a short notice to a client to contact system 
 *	administrator. 
 *
 * Results:
 *	See Ns_ConnReturnNotice 
 *
 * Side effects:
 *	See Ns_ConnReturnNotice 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnAdminNotice(Ns_Conn *conn, int status, char *title, char *notice)
{
    return Ns_ConnReturnNotice(conn, status, title, notice);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnNotice --
 *
 *	Return a short notice to a client. 
 *
 * Results:
 *	See Ns_ReturnHtml. 
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnNotice(Ns_Conn *conn, int status, char *title, char *notice)
{
    Conn *connPtr = (Conn *) conn;
    NsServer *servPtr = connPtr->servPtr;
    Ns_DString ds;
    int        result;

    Ns_DStringInit(&ds);
    if (title == NULL) {
        title = "Server Message";
    }
    Ns_DStringVarAppend(&ds,
			"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
			"<HTML>\n<HEAD>\n"
			"<TITLE>", title, "</TITLE>\n"
			"</HEAD>\n<BODY>\n"
			"<H2>", title, "</H2>\n", NULL);
    if (notice != NULL) {
    	Ns_DStringVarAppend(&ds, notice, "\n", NULL);
    }

    /*
     * Detailed server information at the bottom of the page.
     */
    if (servPtr->opts.noticedetail) {
	Ns_DStringVarAppend(&ds, "<P ALIGN=RIGHT><SMALL><I>",
			    Ns_InfoServerName(), "/",
			    Ns_InfoServerVersion(), " on ",
			    Ns_ConnLocation(conn), "</I></SMALL></P>\n",
			    NULL);
    }

    /*
     * Padding that suppresses those horrible MSIE friendly errors.
     * NB: Because we pad inside the body we may pad more than needed.
     */
    if (status >= 400) {
	while (ds.length < servPtr->limits.errorminsize) {
	    Ns_DStringAppend(&ds, "                    ");
	}
    }
    
    Ns_DStringVarAppend(&ds, "\n</BODY></HTML>\n", NULL);
    
    result = Ns_ConnReturnHtml(conn, status, ds.string, ds.length);
    Ns_DStringFree(&ds);
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnData --
 *
 *	Sets required headers, dumps them, and then writes your data. 
 *
 * Results:
 *	NS_OK/NS_ERROR
 *
 * Side effects:
 *	May set numerous headers, will close connection. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnData(Ns_Conn *conn, int status, char *data, int len, char *type)
{
   return ReturnCharData(conn, status, data, len, type, NS_TRUE);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnCharData --
 *
 *	Sets required headers, dumps them, and then writes your data. 
 *
 * Results:
 *	NS_OK/NS_ERROR
 *
 * Side effects:
 *	May set numerous headers, will close connection. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnCharData(Ns_Conn *conn, int status, char *data, int len, char *type)
{
   return ReturnCharData(conn, status, data, len, type, NS_FALSE);
}


/*
 *----------------------------------------------------------------------
 *
 * ReturnCharData --
 *
 *	Sets required headers, dumps them, and then writes your data. 
 *      If sendRaw is false, then translate the data from utf-8 to the
 *      correct character encoding if appropriate.
 *
 * Results:
 *	NS_OK/NS_ERROR
 *
 * Side effects:
 *	May set numerous headers, will close connection. 
 *
 *----------------------------------------------------------------------
 */

static int
ReturnCharData(Ns_Conn *conn, int status, char *data, int len, char *type,
               int sendRaw)
{
    int          result;
    Conn        *connPtr;
    Tcl_DString  ds;
    Tcl_Encoding enc;
    Tcl_DString  type_ds;
    int          new_type = NS_FALSE;

    connPtr = (Conn *)conn;

    /* Make sure we know what output encoding (if any) to use. */
    if (!sendRaw ) {

        NsComputeEncodingFromType(type, &enc, &new_type, &type_ds);
        if (new_type) {
            type = Tcl_DStringValue(&type_ds);
        }
        if (enc != NULL) {
            connPtr->encoding = enc;
        } else if (connPtr->encoding == NULL) {
            sendRaw = NS_TRUE;
        }
    }

    if (!sendRaw) {

        Tcl_UtfToExternalDString(connPtr->encoding, data, len, &ds);

	data = Tcl_DStringValue(&ds);
	len = Tcl_DStringLength(&ds);

    } else {

	/* Send data unmodified. */
	if (len == -1) {
	    len = data ? strlen(data) : 0;
	}

    }

    Ns_ConnSetRequiredHeaders(conn, type, len);
    Ns_ConnQueueHeaders(conn, status);
    if (conn->flags & NS_CONN_SKIPBODY) {
        data = NULL;
        len = 0;
    }
    result = Ns_WriteConn(conn, data, len);
    if (result == NS_OK) {
        result = Ns_ConnClose(conn);
    }

    if (!sendRaw && (connPtr->encoding != NULL)) {
        Tcl_DStringFree(&ds);
    }
    if (new_type) {
        Tcl_DStringFree(&type_ds);
    }

    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnHtml --
 *
 *	Return data of type text/html to client. 
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	See Ns_ConnReturnData 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnHtml(Ns_Conn *conn, int status, char *html, int len)
{
    return Ns_ConnReturnData(conn, status, html, len, "text/html");
}



/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnOk --
 *
 *	Return a status message to the client. 
 *
 * Results:
 *	See Ns_ReturnStatus 
 *
 * Side effects:
 *	See Ns_ReturnStatus
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnOk(Ns_Conn *conn)
{
    return Ns_ConnReturnStatus(conn, 200);
}



/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnNoResponse --
 *
 *	Return a status message to the client. 
 *
 * Results:
 *	See Ns_ReturnStatus
 *
 * Side effects:
 *	See Ns_ReturnStatus 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnNoResponse(Ns_Conn *conn)
{
    return Ns_ConnReturnStatus(conn, 204);
}



/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnRedirect --
 *
 *	Return a 302 Redirection to the client, or 204 No Content if 
 *	url is null. 
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	Will close connection. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnRedirect(Ns_Conn *conn, char *url)
{
    Ns_DString ds, msg;
    int        result;

    Ns_DStringInit(&ds);
    Ns_DStringInit(&msg);
    if (url != NULL) {
        if (*url == '/') {
            Ns_DStringAppend(&ds, Ns_ConnLocation(conn));
        }
        Ns_DStringAppend(&ds, url);
        Ns_ConnSetHeaders(conn, "Location", ds.string);
	Ns_DStringVarAppend(&msg, "<A HREF=\"", ds.string,
		"\">The requested URL has moved here.</A>", NULL);
	result = Ns_ConnReturnNotice(conn, 302, "Redirection", msg.string);
    } else {
	result = Ns_ConnReturnNotice(conn, 204, "No Content", msg.string);
    }
    Ns_DStringFree(&msg);
    Ns_DStringFree(&ds);
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnBadRequest --
 *
 *	Return an 'invalid request' HTTP status line with an error 
 *	message. 
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	Will close connection. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnBadRequest(Ns_Conn *conn, char *reason)
{
    Ns_DString ds;
    int        result;

    if (ReturnRedirect(conn, 400, &result)) {
	return result;
    }
    Ns_DStringInit(&ds);
    Ns_DStringAppend(&ds,
	"The HTTP request presented by your browser is invalid.");
    if (reason != NULL) {
        Ns_DStringVarAppend(&ds, "<P>\n", reason, NULL);
    }
    result = Ns_ConnReturnNotice(conn, 400, "Invalid Request", ds.string);
    Ns_DStringFree(&ds);
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnUnauthorized --
 *
 *	Return a 401 Unauthorized response, which will prompt the 
 *	user for a Basic authentication username/password. 
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	Will close the connection. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnUnauthorized(Ns_Conn *conn)
{
    Conn *connPtr = (Conn *) conn;
    Ns_DString  ds;
    int	        result;

    Ns_DStringInit(&ds);
    Ns_DStringVarAppend(&ds, "Basic realm=\"",
	connPtr->servPtr->opts.realm, "\"", NULL);
    Ns_ConnSetHeaders(conn, "WWW-Authenticate", ds.string);
    Ns_DStringFree(&ds);

    if (ReturnRedirect(conn, 401, &result)) {
        return result;
    }
    return Ns_ConnReturnNotice(conn, 401, "Access Denied",
	"The requested URL cannot be accessed because a "
	"valid username and password are required.");
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnForbidden --
 *
 *	Return a 403 Forbidden response. 
 *
 * Results:
 *	NS_OK/NS_ERROR. 
 *
 * Side effects:
 *	Will close the connection. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnForbidden(Ns_Conn *conn)
{
    int result;

    if (ReturnRedirect(conn, 403, &result)) {
	return result;
    }
    return Ns_ConnReturnNotice(conn, 403, "Forbidden",
	"The requested URL cannot be accessed by this server.");
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnNotFound --
 *
 *	Return a 404 Not Found response. 
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	Will close the connection. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnNotFound(Ns_Conn *conn)
{
    int result;

    if (ReturnRedirect(conn, 404, &result)) {
	return result;
    }
    return Ns_ConnReturnNotice(conn, 404, "Not Found",
	"The requested URL was not found on this server.");
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnNotModified --
 *
 *	Return a 304 Not Modified response. 
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	Will close the connection. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnNotModified(Ns_Conn *conn)
{
    return Ns_ConnReturnStatus(conn, 304);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnNotImplemented --
 *
 *	Return a 501 Not Implemented response. 
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	Will close the connection. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnNotImplemented(Ns_Conn *conn)
{
    int result;

    if (ReturnRedirect(conn, 501, &result)) {
	return result;
    }
    return Ns_ConnReturnNotice(conn, 501, "Not Implemented",
	"The requested URL or method is not implemented "
	"by this server.");
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnInternalError --
 *
 *	Return a 500 Internal Error response. 
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	Will close the connection. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnInternalError(Ns_Conn *conn)
{
    int result;

    Ns_SetTrunc(conn->outputheaders, 0);
    if (ReturnRedirect(conn, 500, &result)) {
	return result;
    }
    return Ns_ConnReturnNotice(conn, 500, "Server Error",
	"The requested URL cannot be accessed "
	"due to a system error on this server.");
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnStatus --
 *
 *	Return an arbitrary status code.
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	Will close the connection. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnStatus(Ns_Conn *conn, int status)
{
    int result;
    
    if (ReturnRedirect(conn, status, &result)) {
    	return result;
    }
    Ns_ConnSetRequiredHeaders(conn, NULL, 0);
    Ns_ConnFlushHeaders(conn, status);
    return Ns_ConnClose(conn);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnOpenChannel --
 *
 *	Send an open channel out the conn. 
 *
 * Results:
 *	See ReturnOpen. 
 *
 * Side effects:
 *	See ReturnOpen. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnOpenChannel(Ns_Conn *conn, int status, char *type,
			 Tcl_Channel chan, int len)
{
    return ReturnOpen(conn, status, type, chan, NULL, -1, len);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnOpenFile --
 *
 *	Send an open file out the conn. 
 *
 * Results:
 *	See ReturnOpen. 
 *
 * Side effects:
 *	See ReturnOpen. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnOpenFile(Ns_Conn *conn, int status, char *type, FILE *fp, int len)
{
    return ReturnOpen(conn, status, type, NULL, fp, -1, len);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ConnReturnOpenFd --
 *
 *	Send an open fd out the conn. 
 *
 * Results:
 *	See ReturnOpen. 
 *
 * Side effects:
 *	See ReturnOpen. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ConnReturnOpenFd(Ns_Conn *conn, int status, char *type, int fd, int len)
{
    return ReturnOpen(conn, status, type, NULL, NULL, fd, len);
}


/*
 *----------------------------------------------------------------------
 *
 * ReturnRedirect --
 *
 *	Return the appropriate redirect for a given status code. 
 *
 * Results:
 *	0 if no redir exists, 1 if one does. 
 *
 * Side effects:
 *	May write and close the conn. 
 *
 *----------------------------------------------------------------------
 */

static int
ReturnRedirect(Ns_Conn *conn, int status, int *resultPtr)
{
    Tcl_HashEntry *hPtr;
    Conn    	  *connPtr;
    NsServer      *servPtr;

    connPtr = (Conn *) conn;
    servPtr = connPtr->servPtr;
    hPtr = Tcl_FindHashEntry(&servPtr->request.redirect, (char *) status);
    if (hPtr != NULL) {
	if (++connPtr->recursionCount > MAX_RECURSION) {
	    Ns_Log(Error, "return: failed to redirect '%d': "
		   "exceeded recursion limit of %d", status, MAX_RECURSION);
	} else {
    	    *resultPtr = Ns_ConnRedirect(conn, Tcl_GetHashValue(hPtr));
	    return 1;
	}
    }
    return 0;
}


/*
 *----------------------------------------------------------------------
 *
 * ReturnOpen --
 *
 *	Dump an open 'something' to the conn. 
 *
 * Results:
 *	NS_OK/NS_ERROR. 
 *
 * Side effects:
 *	Will close the connection on success. 
 *
 *----------------------------------------------------------------------
 */

static int
ReturnOpen(Ns_Conn *conn, int status, char *type, Tcl_Channel chan,
	FILE *fp, int fd, int len)
{
    int result;

    Ns_ConnSetRequiredHeaders(conn, type, len);
    Ns_ConnQueueHeaders(conn, status);
    if (chan != NULL) {
	result = Ns_ConnSendChannel(conn, chan, len);
    } else if (fp != NULL) {
	result = Ns_ConnSendFp(conn, fp, len);
    } else {
	result = Ns_ConnSendFd(conn, fd, len);
    }
    if (result == NS_OK) {
        result = Ns_ConnClose(conn);
    }
    return result;
}
