/* * 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. */ /* * connio.c -- * * Handle connection I/O. */ static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/connio.c,v 1.27 2006/04/19 17:48:43 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__; #include "nsd.h" #define IOBUFSZ 2048 /* * Local functions defined in this file */ static int ConnSend(Ns_Conn *conn, int nsend, Tcl_Channel chan, FILE *fp, int fd, off_t off); static int ConnCopy(Ns_Conn *conn, size_t ncopy, Ns_DString *dsPtr, Tcl_Channel chan, FILE *fp, int fd); /* *----------------------------------------------------------------- * * Ns_ConnInit -- * * Initialize a connection, currently doing nothing. * * Results: * Always NS_OK. * * Side effects: * None. * *----------------------------------------------------------------- */ int Ns_ConnInit(Ns_Conn *conn) { return NS_OK; } /* *----------------------------------------------------------------- * * Ns_ConnClose - Close a connection. * * Results: * Always NS_OK. * * Side effects: * The underlying socket in the connection is closed or moved * to the waiting keep-alive list. * *----------------------------------------------------------------- */ int Ns_ConnClose(Ns_Conn *conn) { Conn *connPtr = (Conn *)conn; int keep; if (connPtr->sockPtr != NULL) { Ns_GetTime(&connPtr->times.close); keep = (conn->flags & NS_CONN_KEEPALIVE) ? 1 : 0; NsSockClose(connPtr->sockPtr, keep); connPtr->sockPtr = NULL; connPtr->flags |= NS_CONN_CLOSED; if (connPtr->itPtr != NULL) { NsTclRunAtClose(connPtr->itPtr); } } return NS_OK; } /* *---------------------------------------------------------------------- * * Ns_ConnFlush -- * * Flush the headers and/or response content, handling character * encoding and/or gzip compression if necessary. * * Results: * NS_ERROR if a connection write routine failed, NS_OK otherwise. * * Side effects: * Content may be encoded and/or gzip'ed before calling * Ns_ConnFlushDirect. * *---------------------------------------------------------------------- */ int Ns_ConnFlush(Ns_Conn *conn, char *buf, int len, int stream) { Conn *connPtr = (Conn *) conn; NsServer *servPtr = connPtr->servPtr; Tcl_Encoding encoding; Tcl_DString enc, gzip; char *ahdr; int status; Tcl_DStringInit(&enc); Tcl_DStringInit(&gzip); if (len < 0) { len = strlen(buf); } /* * Encode content to the expected charset. */ encoding = Ns_ConnGetEncoding(conn); if (encoding != NULL) { Tcl_UtfToExternalDString(encoding, buf, len, &enc); buf = enc.string; len = enc.length; } /* * GZIP the content when not streaming if enabled and the content * length is above the minimum. */ if (!stream && (conn->flags & NS_CONN_GZIP) && (servPtr->opts.flags & SERV_GZIP) && (len > (int) servPtr->opts.gzipmin) && (ahdr = Ns_SetIGet(conn->headers, "Accept-Encoding")) != NULL && strstr(ahdr, "gzip") != NULL && Ns_Gzip(buf, len, servPtr->opts.gziplevel, &gzip) == NS_OK) { buf = gzip.string; len = gzip.length; Ns_ConnCondSetHeaders(conn, "Content-Encoding", "gzip"); } /* * Flush content. */ status = Ns_ConnFlushDirect(conn, buf, len, stream); Tcl_DStringFree(&enc); Tcl_DStringFree(&gzip); return status; } /* *---------------------------------------------------------------------- * * Ns_ConnFlushDirect -- * * Flush the headers and/or response content either as a single * response or in stream/chunked mode. * * Results: * NS_ERROR if a connection write routine failed, NS_OK otherwise. * * Side effects: * Headers will be flushed on first write. Output will be * chunked if streaming to an HTTP version 1.1 or greater client. * *---------------------------------------------------------------------- */ int Ns_ConnFlushDirect(Ns_Conn *conn, char *buf, int len, int stream) { struct iovec iov[4]; int i, nwrote, towrite, hlen, ioc; char hdr[100]; /* * Queue headers if not already sent. */ if (!(conn->flags & NS_CONN_SENTHDRS)) { if (!stream) { hlen = len; } else { if (conn->request->version > 1.0) { conn->flags |= NS_CONN_CHUNK; } hlen = -1; /* NB: Surpress Content-length header. */ } Ns_ConnSetRequiredHeaders(conn, Ns_ConnGetType(conn), hlen); if (conn->flags & NS_CONN_CHUNK) { Ns_ConnCondSetHeaders(conn, "Transfer-Encoding", "chunked"); } Ns_ConnQueueHeaders(conn, Ns_ConnGetStatus(conn)); } /* * Send content on any request other than HEAD. */ ioc = 0; towrite = 0; if (!(conn->flags & NS_CONN_SKIPBODY)) { if (!(conn->flags & NS_CONN_CHUNK)) { /* * Output content without chunking header/trailers. */ iov[ioc].iov_base = buf; iov[ioc++].iov_len = len; } else { if (len > 0) { /* * Output length header followed by content and trailer. */ iov[ioc].iov_base = hdr; iov[ioc++].iov_len = sprintf(hdr, "%x\r\n", len); iov[ioc].iov_base = buf; iov[ioc++].iov_len = len; iov[ioc].iov_base = "\r\n"; iov[ioc++].iov_len = 2; } if (!stream) { /* * Output end-of-content trailer. */ iov[ioc].iov_base = "0\r\n\r\n"; iov[ioc++].iov_len = 5; } } for (i = 0; i < ioc; ++i) { towrite += iov[i].iov_len; } } /* * Write the output buffer and if not streaming, close the * connection. */ nwrote = Ns_ConnSend(conn, iov, ioc); if (nwrote != towrite) { return NS_ERROR; } if (!stream && Ns_ConnClose(conn) != NS_OK) { return NS_ERROR; } return NS_OK; } /* *---------------------------------------------------------------------- * * Ns_ConnSend -- * * Sends buffers to clients, including any queued * write-behind data if necessary. Unlike in * previous versions of AOLserver, this routine * attempts to send all data if possible. * * Results: * Number of bytes written, -1 for error on first send. * * Side effects: * Will truncate queued data after send. * *---------------------------------------------------------------------- */ int Ns_ConnSend(Ns_Conn *conn, struct iovec *bufs, int nbufs) { Conn *connPtr = (Conn *) conn; int nwrote, towrite, i, n; struct iovec sbufs[16]; /* * Send up to 16 buffers, including the queued output * buffer if necessary. */ towrite = 0; n = 0; if (connPtr->obuf.length > 0) { sbufs[n].iov_base = connPtr->obuf.string; sbufs[n].iov_len = connPtr->obuf.length; towrite += sbufs[n].iov_len; ++n; } for (i = 0; i < nbufs && n < 16; ++i) { if (bufs[i].iov_len > 0 && bufs[i].iov_base != NULL) { sbufs[n].iov_base = bufs[i].iov_base; sbufs[n].iov_len = bufs[i].iov_len; towrite += bufs[i].iov_len; ++n; } } nbufs = n; bufs = sbufs; n = nwrote = 0; while (towrite > 0) { n = NsConnSend(conn, bufs, nbufs); if (n < 0) { break; } towrite -= n; nwrote += n; if (towrite > 0) { for (i = 0; i < nbufs && n > 0; ++i) { if (n > (int) bufs[i].iov_len) { n -= bufs[i].iov_len; bufs[i].iov_base = NULL; bufs[i].iov_len = 0; } else { bufs[i].iov_base = (char *) bufs[i].iov_base + n; bufs[i].iov_len -= n; n = 0; } } } } if (nwrote > 0) { connPtr->nContentSent += nwrote; if (connPtr->obuf.length > 0) { n = connPtr->obuf.length - nwrote; if (n <= 0) { nwrote -= connPtr->obuf.length; Tcl_DStringTrunc(&connPtr->obuf, 0); } else { memmove(connPtr->obuf.string, connPtr->obuf.string + nwrote, (size_t)n); Tcl_DStringTrunc(&connPtr->obuf, n); nwrote = 0; } } } else { /* * Return error on first send, if any, from NsSockSend above. */ nwrote = n; } return nwrote; } /* *---------------------------------------------------------------------- * * Ns_ConnWrite -- * * Send a single buffer to the client. * * Results: * # of bytes written from buffer or -1 on error. * * Side effects: * Stuff may be written * *---------------------------------------------------------------------- */ int Ns_ConnWrite(Ns_Conn *conn, void *vbuf, int towrite) { struct iovec buf; buf.iov_base = vbuf; buf.iov_len = towrite; return Ns_ConnSend(conn, &buf, 1); } /* *---------------------------------------------------------------------- * * Ns_WriteConn -- * * This will write a buffer to the conn. It promises to write * all of it. * * Results: * NS_OK/NS_ERROR * * Side effects: * Stuff may be written * *---------------------------------------------------------------------- */ int Ns_WriteConn(Ns_Conn *conn, char *buf, int len) { if (Ns_ConnWrite(conn, buf, len) != len) { return NS_ERROR; } return NS_OK; } /* *---------------------------------------------------------------------- * * Ns_WriteCharConn -- * * This will write a string buffer to the conn. The distinction * being that the given data is explicitly a UTF8 character string, * and will be put out in an 'encoding-aware' manner. * It promises to write all of it. * * If we think we are writing the headers (which is the default), * then we send the data exactly as it is given to us. If we are * truly in the headers, then they are supposed to be US-ASCII, * which is a subset of UTF-8, so no translation should be needed * if the user has been good and not put any 8-bit characters * into it. * * If we have been told that we are sending the content, and we * have been given an encoding to translate the content to, then * we assume that the caller is handing us UTF-8 bytes and we * translate them to the preset encoding. * * Results: * NS_OK/NS_ERROR * * Side effects: * Stuff may be written * *---------------------------------------------------------------------- */ int Ns_WriteCharConn(Ns_Conn *conn, char *buf, int len) { Tcl_Encoding encoding; Tcl_DString enc; int status; Tcl_DStringInit(&enc); encoding = Ns_ConnGetEncoding(conn); if (encoding != NULL) { Tcl_UtfToExternalDString(encoding, buf, len, &enc); buf = enc.string; len = enc.length; } status = Ns_WriteConn(conn, buf, len); Tcl_DStringFree(&enc); return status; } /* *---------------------------------------------------------------------- * * Ns_ConnPuts -- * * Write a null-terminated string to the conn; no trailing * newline will be appended despite the name. * * Results: * See Ns_WriteConn * * Side effects: * See Ns_WriteConn * *---------------------------------------------------------------------- */ int Ns_ConnPuts(Ns_Conn *conn, char *string) { return Ns_WriteConn(conn, string, (int)strlen(string)); } /* *---------------------------------------------------------------------- * * Ns_ConnSendDString -- * * Write contents of a DString * * Results: * See Ns_WriteConn * * Side effects: * See Ns_WriteConn * *---------------------------------------------------------------------- */ int Ns_ConnSendDString(Ns_Conn *conn, Ns_DString *dsPtr) { return Ns_WriteConn(conn, dsPtr->string, dsPtr->length); } /* *---------------------------------------------------------------------- * * Ns_ConnSendChannel, Fp, Fd -- * * Send an open channel, FILE, or fd. * * Results: * NS_OK/NS_ERROR * * Side effects: * See ConnSend(). * *---------------------------------------------------------------------- */ int Ns_ConnSendChannel(Ns_Conn *conn, Tcl_Channel chan, int nsend) { return ConnSend(conn, nsend, chan, NULL, -1, -1); } int Ns_ConnSendFp(Ns_Conn *conn, FILE *fp, int nsend) { return ConnSend(conn, nsend, NULL, fp, -1, -1); } int Ns_ConnSendFd(Ns_Conn *conn, int fd, int nsend) { return ConnSend(conn, nsend, NULL, NULL, fd, -1); } int Ns_ConnSendFdEx(Ns_Conn *conn, int fd, off_t off, int nsend) { return ConnSend(conn, nsend, NULL, NULL, fd, off); } /* *---------------------------------------------------------------------- * * Ns_ConnFlushContent -- * * Finish reading waiting content. * * Results: * NS_OK. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Ns_ConnFlushContent(Ns_Conn *conn) { int avail; if (NsConnContent(conn, NULL, &avail) == NULL) { return NS_ERROR; } NsConnSeek(conn, avail); return NS_OK; } /* *---------------------------------------------------------------------- * * Ns_ConnGets -- * * Read in a string from a connection, stopping when either * we've run out of data, hit a newline, or had an error * * Results: * Pointer to given buffer or NULL on error. * * Side effects: * * *---------------------------------------------------------------------- */ char * Ns_ConnGets(char *buf, size_t bufsize, Ns_Conn *conn) { char *p; p = buf; while (bufsize > 1) { if (Ns_ConnRead(conn, p, 1) != 1) { return NULL; } if (*p++ == '\n') { break; } --bufsize; } *p = '\0'; return buf; } /* *---------------------------------------------------------------------- * * Ns_ConnRead -- * * Copy data from read-ahead buffers. * * Results: * Number of bytes copied. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Ns_ConnRead(Ns_Conn *conn, void *vbuf, int toread) { int avail; char *next; if (NsConnContent(conn, &next, &avail) == NULL) { return -1; } if (toread > avail) { toread = avail; } memcpy(vbuf, next, (size_t) toread); NsConnSeek(conn, toread); return toread; } /* *---------------------------------------------------------------------- * * Ns_ConnReadLine -- * * Read a line (\r\n or \n terminated) from the conn. * * Results: * NS_OK or NS_ERROR * * Side effects: * Stuff may be read * *---------------------------------------------------------------------- */ int Ns_ConnReadLine(Ns_Conn *conn, Ns_DString *dsPtr, int *nreadPtr) { Conn *connPtr = (Conn *) conn; Driver *drvPtr = connPtr->drvPtr; char *eol, *next; int nread, ncopy, avail; if (NsConnContent(conn, &next, &avail) == NULL) { return NS_ERROR; } eol = memchr(next, '\n', avail); if (eol == NULL) { eol = next + avail; } nread = eol - next; if (nread > drvPtr->maxline) { return NS_ERROR; } ncopy = nread; ++nread; if (nreadPtr != NULL) { *nreadPtr = nread; } if (ncopy > 0 && eol[-1] == '\r') { --ncopy; } Ns_DStringNAppend(dsPtr, next, ncopy); NsConnSeek(conn, nread); return NS_OK; } /* *---------------------------------------------------------------------- * * Ns_ConnReadHeaders -- * * Read the headers and insert them into the passed-in set * * Results: * NS_OK/NS_ERROR * * Side effects: * Stuff will be read from the conn * *---------------------------------------------------------------------- */ int Ns_ConnReadHeaders(Ns_Conn *conn, Ns_Set *set, int *nreadPtr) { Ns_DString ds; Conn *connPtr = (Conn *) conn; NsServer *servPtr = connPtr->servPtr; int status, nread, nline, maxhdr; Ns_DStringInit(&ds); nread = 0; maxhdr = connPtr->drvPtr->maxinput; status = NS_OK; while (nread < maxhdr && status == NS_OK) { Ns_DStringTrunc(&ds, 0); status = Ns_ConnReadLine(conn, &ds, &nline); if (status == NS_OK) { nread += nline; if (nread > maxhdr) { status = NS_ERROR; } else { if (ds.string[0] == '\0') { break; } status = Ns_ParseHeader(set, ds.string, servPtr->opts.hdrcase); } } } if (nreadPtr != NULL) { *nreadPtr = nread; } Ns_DStringFree(&ds); return status; } /* *---------------------------------------------------------------------- * * Ns_ConnCopyToDString, File, Fd, Channel -- * * Copy data from a connection to a DString, channel, FILE, or fd. * * Results: * NS_OK or NS_ERROR * * Side effects: * See ConnCopy(). * *---------------------------------------------------------------------- */ int Ns_ConnCopyToDString(Ns_Conn *conn, size_t ncopy, Ns_DString *dsPtr) { return ConnCopy(conn, ncopy, dsPtr, NULL, NULL, -1); } int Ns_ConnCopyToChannel(Ns_Conn *conn, size_t ncopy, Tcl_Channel chan) { return ConnCopy(conn, ncopy, NULL, chan, NULL, -1); } int Ns_ConnCopyToFile(Ns_Conn *conn, size_t ncopy, FILE *fp) { return ConnCopy(conn, ncopy, NULL, NULL, fp, -1); } int Ns_ConnCopyToFd(Ns_Conn *conn, size_t ncopy, int fd) { return ConnCopy(conn, ncopy, NULL, NULL, NULL, fd); } /* *---------------------------------------------------------------------- * * ConnCopy -- * * Copy connection content to a DString, channel, FILE, or fd. * * Results: * NS_OK or NS_ERROR if not all content could be read. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int ConnCopy(Ns_Conn *conn, size_t tocopy, Ns_DString *dsPtr, Tcl_Channel chan, FILE *fp, int fd) { int nwrote, avail; int ncopy = (int) tocopy; char *next; if (NsConnContent(conn, &next, &avail) == NULL || avail < ncopy) { return NS_ERROR; } while (ncopy > 0) { if (dsPtr != NULL) { Ns_DStringNAppend(dsPtr, next, ncopy); nwrote = ncopy; } else if (chan != NULL) { nwrote = Tcl_Write(chan, next, ncopy); } else if (fp != NULL) { nwrote = fwrite(next, 1, (size_t) ncopy, fp); if (ferror(fp)) { nwrote = -1; } } else { nwrote = write(fd, next, (size_t)ncopy); } if (nwrote < 0) { return NS_ERROR; } ncopy -= nwrote; next += nwrote; NsConnSeek(conn, nwrote); } return NS_OK; } /* *---------------------------------------------------------------------- * * ConnSend -- * * Send content from a channel, FILE, or fd. * * Results: * NS_OK or NS_ERROR if a write failed. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int ConnSend(Ns_Conn *conn, int nsend, Tcl_Channel chan, FILE *fp, int fd, off_t off) { size_t toread; int nread, status; char buf[IOBUFSZ]; /* * Even if nsend is 0, ensure all queued data (like HTTP response * headers) get flushed. */ if (nsend == 0) { Ns_WriteConn(conn, NULL, 0); } status = NS_OK; while (status == NS_OK && nsend > 0) { toread = (size_t) nsend; if (toread > sizeof(buf)) { toread = sizeof(buf); } if (chan != NULL) { nread = Tcl_Read(chan, buf, (int) toread); } else if (fp != NULL) { nread = fread(buf, 1, toread, fp); if (ferror(fp)) { nread = -1; } } else if (off < 0) { nread = read(fd, buf, toread); } else { #ifdef WIN32 nread = -1; #else nread = pread(fd, buf, toread, off); if (nread > 0) { off += (off_t) nread; } #endif } if (nread == -1) { status = NS_ERROR; } else if (nread == 0) { nsend = 0; /* NB: Silently ignore a truncated file. */ } else if ((status = Ns_WriteConn(conn, buf, nread)) == NS_OK) { nsend -= nread; } } return status; }