/*
Copyright (c) 2003-2006 by Juliusz Chroboczek

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#include "polipo.h"

static int 
httpAcceptAgain(TimeEventHandlerPtr event)
{
    FdEventHandlerPtr newevent;
    int fd = *(int*)event->data;

    newevent = schedule_accept(fd, httpAccept, NULL);
    if(newevent == NULL) {
        free_chunk_arenas();
        newevent = schedule_accept(fd, httpAccept, NULL);
        if(newevent == NULL) {
            do_log(L_ERROR, "Couldn't schedule accept.\n");
            polipoExit();
        }
    }
    return 1;
}

int
httpAccept(int fd, FdEventHandlerPtr event, AcceptRequestPtr request)
{
    int rc;
    HTTPConnectionPtr connection;
    TimeEventHandlerPtr timeout;

    if(fd < 0) {
        if(-fd == EINTR || -fd == EAGAIN || -fd == EWOULDBLOCK)
            return 0;
        do_log_error(L_ERROR, -fd, "Couldn't establish listening socket");
        if(-fd == EMFILE || -fd == ENOMEM || -fd == ENOBUFS) {
            TimeEventHandlerPtr again = NULL;
            do_log(L_WARN, "Refusing client connections for one second.\n");
            free_chunk_arenas();
            again = scheduleTimeEvent(1, httpAcceptAgain, 
                                      sizeof(request->fd), &request->fd);
            if(!again) {
                do_log(L_ERROR, "Couldn't schedule accept -- sleeping.\n");
                sleep(1);
                again = scheduleTimeEvent(1, httpAcceptAgain, 
                                          sizeof(request->fd), &request->fd);
                if(!again) {
                    do_log(L_ERROR, "Couldn't schedule accept -- aborting.\n");
                    polipoExit();
                }
            }
            return 1;
        } else {
            polipoExit();
            return 1;
        }
    }

    if(allowedNets) {
        if(netAddressMatch(fd, allowedNets) != 1) {
            do_log(L_WARN, "Refusing connection from unauthorised net\n");
            CLOSE(fd);
            return 0;
        }
    }

    rc = setNonblocking(fd, 1);
    if(rc < 0) {
        do_log_error(L_WARN, errno, "Couldn't set non blocking mode");
        CLOSE(fd);
        return 0;
    }
    rc = setNodelay(fd, 1);
    if(rc < 0) 
        do_log_error(L_WARN, errno, "Couldn't disable Nagle's algorithm");

    connection = httpMakeConnection();

    timeout = scheduleTimeEvent(clientTimeout, httpTimeoutHandler,
                                sizeof(connection), &connection);
    if(!timeout) {
        CLOSE(fd);
        free(connection);
        return 0;
    }

    connection->fd = fd;
    connection->timeout = timeout;

    do_log(D_CLIENT_CONN, "Accepted client connection 0x%lx\n",
           (unsigned long)connection);

    connection->flags = CONN_READER;

    do_stream_buf(IO_READ | IO_NOTNOW, connection->fd, 0,
                  &connection->reqbuf, CHUNK_SIZE,
                  httpClientHandler, connection);
    return 0;
}

/* Abort a client connection.  It is only safe to abort the requests
   if we know the connection is closed. */
void
httpClientAbort(HTTPConnectionPtr connection, int closed)
{
    HTTPRequestPtr request = connection->request;

    pokeFdEvent(connection->fd, -EDOSHUTDOWN, POLLOUT);
    if(closed) {
        while(request) {
            if(request->chandler) {
                request->error_code = 500;
                request->error_message = internAtom("Connection finishing");
                abortConditionHandler(request->chandler);
                request->chandler = NULL;
            }
            request = request->next;
        }
    }
}

/* s != 0 specifies that the connection must be shut down.  It is 1 in
   order to linger the connection, 2 to close it straight away. */
void
httpClientFinish(HTTPConnectionPtr connection, int s)
{
    HTTPRequestPtr request = connection->request;

    assert(!(request && request->request 
             && request->request->request != request));

    if(s == 0) {
        if(!request || !(request->flags & REQUEST_PERSISTENT))
            s = 1;
    }

    httpConnectionDestroyBuf(connection);

    connection->flags &= ~CONN_WRITER;

    if(connection->flags & CONN_SIDE_READER) {
        /* We're in POST or PUT and the reader isn't done yet.
           Wait for the read side to close the connection. */
        assert(request && (connection->flags & CONN_READER));
        if(s >= 2) {
            pokeFdEvent(connection->fd, -EDOSHUTDOWN, POLLIN);
        } else {
            pokeFdEvent(connection->fd, -EDOGRACEFUL, POLLIN);
        }
        return;
    }

    if(connection->timeout) 
        cancelTimeEvent(connection->timeout);
    connection->timeout = NULL;

    if(request) {
        HTTPRequestPtr requestee;

        requestee = request->request;
        if(requestee) {
            request->request = NULL;
            requestee->request = NULL;
        }
        if(requestee)
            httpServerClientReset(requestee);
        if(request->chandler) {
            request->error_code = 500;
            request->error_message = internAtom("Connection finishing");
            abortConditionHandler(request->chandler);
            request->chandler = NULL;
        }
            
        if(request->object) {
            if(request->object->requestor == request)
                request->object->requestor = NULL;
            releaseObject(request->object);
            request->object = NULL;
        }
        httpDequeueRequest(connection);
        httpDestroyRequest(request);
        request = NULL;

    }

    connection->len = -1;
    connection->offset = 0;
    connection->te = TE_IDENTITY;

    if(!s) {
        assert(connection->fd > 0);
        connection->serviced++;
        httpSetTimeout(connection, clientTimeout);
        if(!connection->flags & CONN_READER) {
            if(connection->reqlen == 0)
                httpConnectionDestroyReqbuf(connection);
            else if((connection->flags & CONN_BIGREQBUF) &&
                    connection->reqlen < CHUNK_SIZE)
                httpConnectionUnbigifyReqbuf(connection);
            connection->flags |= CONN_READER;
            httpSetTimeout(connection, clientTimeout);
            do_stream_buf(IO_READ | IO_NOTNOW |
                          (connection->reqlen ? IO_IMMEDIATE : 0),
                          connection->fd, connection->reqlen,
                          &connection->reqbuf,
                          (connection->flags & CONN_BIGREQBUF) ?
                          bigBufferSize : CHUNK_SIZE,
                          httpClientHandler, connection);
        }
        /* The request has already been validated when it first got
           into the queue */
        if(connection->request) {
            if(connection->request->object != NULL)
                httpClientNoticeRequest(connection->request, 1);
            else
                assert(connection->flags & CONN_READER);
        }
        return;
    }
    
    do_log(D_CLIENT_CONN, "Closing client connection 0x%lx\n",
           (unsigned long)connection);

    if(connection->flags & CONN_READER) {
        httpSetTimeout(connection, 10);
        if(connection->fd < 0) return;
        if(s >= 2) {
            pokeFdEvent(connection->fd, -EDOSHUTDOWN, POLLIN);
        } else {
            pokeFdEvent(connection->fd, -EDOGRACEFUL, POLLIN);
        }
        return;
    }
    while(1) {
        HTTPRequestPtr requestee;
        request = connection->request;
        if(!request)
            break;
        requestee = request->request;
        request->request = NULL;
        if(requestee) {
            requestee->request = NULL;
            httpServerClientReset(requestee);
        }
        if(request->chandler)
            abortConditionHandler(request->chandler);
        request->chandler = NULL;
        if(request->object && request->object->requestor == request)
            request->object->requestor = NULL;
        httpDequeueRequest(connection);
        httpDestroyRequest(request);
    }
    httpConnectionDestroyReqbuf(connection);
    if(connection->timeout)
        cancelTimeEvent(connection->timeout);
    connection->timeout = NULL;
    if(connection->fd >= 0) {
        if(s >= 2)
            CLOSE(connection->fd);
        else
            lingeringClose(connection->fd);
    }
    connection->fd = -1;
    free(connection);
}

/* Extremely baroque implementation of close: we need to synchronise
   between the writer and the reader.  */

static char client_shutdown_buffer[17];

static int httpClientDelayedShutdownHandler(TimeEventHandlerPtr);

static int
httpClientDelayedShutdown(HTTPConnectionPtr connection)
{
    TimeEventHandlerPtr handler;

    assert(connection->flags & CONN_READER);
    handler = scheduleTimeEvent(1, httpClientDelayedShutdownHandler,
                                sizeof(connection), &connection);
    if(!handler) {
        do_log(L_ERROR, 
               "Couldn't schedule delayed shutdown -- freeing memory.");
        free_chunk_arenas();
        handler = scheduleTimeEvent(1, httpClientDelayedShutdownHandler,
                                    sizeof(connection), &connection);
        if(!handler) {
            do_log(L_ERROR, 
                   "Couldn't schedule delayed shutdown -- aborting.\n");
            polipoExit();
        }
    }
    return 1;
}

static int
httpClientShutdownHandler(int status,
                          FdEventHandlerPtr event, StreamRequestPtr request)
{
    HTTPConnectionPtr connection = request->data;

    assert(connection->flags & CONN_READER);

    if(!(connection->flags & CONN_WRITER)) {
        connection->flags &= ~CONN_READER;
        connection->reqlen = 0;
        httpConnectionDestroyReqbuf(connection);
        if(status && status != -EDOGRACEFUL)
            httpClientFinish(connection, 2);
        else
            httpClientFinish(connection, 1);
        return 1;
    }

    httpClientDelayedShutdown(connection);
    return 1;
}

static int 
httpClientDelayedShutdownHandler(TimeEventHandlerPtr event)
{
    HTTPConnectionPtr connection = *(HTTPConnectionPtr*)event->data;
    assert(connection->flags & CONN_READER);

    if(!(connection->flags & CONN_WRITER)) {
        connection->flags &= ~CONN_READER;
        connection->reqlen = 0;
        httpConnectionDestroyReqbuf(connection);
        httpClientFinish(connection, 1);
        return 1;
    }
    do_stream(IO_READ | IO_NOTNOW, connection->fd, 
              0, client_shutdown_buffer, 17, 
              httpClientShutdownHandler, connection);
    return 1;
}

int
httpClientHandler(int status,
                  FdEventHandlerPtr event, StreamRequestPtr request)
{
    HTTPConnectionPtr connection = request->data;
    int i, body;
    int bufsize = 
        (connection->flags & CONN_BIGREQBUF) ? connection->reqlen : CHUNK_SIZE;

    assert(connection->flags & CONN_READER);

    /* There's no point trying to do something with this request if
       the client has shut the connection down -- HTTP doesn't do
       half-open connections. */
    if(status != 0) {
        connection->reqlen = 0;
        httpConnectionDestroyReqbuf(connection);
        if(!(connection->flags & CONN_WRITER)) {
            connection->flags &= ~CONN_READER;
            if(status > 0 || status == -ECONNRESET || status == -EDOSHUTDOWN)
                httpClientFinish(connection, 2);
            else
                httpClientFinish(connection, 1);
            return 1;
        }
        httpClientAbort(connection, status > 0 || status == -ECONNRESET);
        connection->flags &= ~CONN_READER;
        return 1;
    }

    i = findEndOfHeaders(connection->reqbuf, 0, request->offset, &body);
    connection->reqlen = request->offset;

    if(i >= 0) {
        connection->reqbegin = i;
        httpClientHandlerHeaders(event, request, connection);
        return 1;
    }

    if(status) {
        if(connection->reqlen > 0) {
            if(connection->serviced <= 0)
                do_log(L_ERROR, "Client dropped connection.\n");
            else
                do_log(D_CLIENT_CONN, "Client dropped idle connection.\n");
        }
        connection->flags &= ~CONN_READER;
        if(!connection->request)
            httpClientFinish(connection, 2);
        else
            pokeFdEvent(connection->fd, -EDOGRACEFUL, POLLOUT);
        return 1;
    }

    if(connection->reqlen >= bufsize) {
        int rc = 0;
        if(!(connection->flags & CONN_BIGREQBUF))
            rc = httpConnectionBigifyReqbuf(connection);
        if((connection->flags & CONN_BIGREQBUF) &&
           connection->reqlen < bigBufferSize) {
            do_stream(IO_READ, connection->fd, connection->reqlen,
                      connection->reqbuf, bigBufferSize,
                      httpClientHandler, connection);
            return 1;
        }
        connection->reqlen = 0;
        httpConnectionDestroyReqbuf(connection);
        if(rc < 0) {
            do_log(L_ERROR, "Couldn't allocate big buffer.\n");
            httpClientNewError(connection, METHOD_UNKNOWN, 0, 400, 
                               internAtom("Couldn't allocate big buffer"));
        } else {
            do_log(L_ERROR, "Couldn't find end of client's headers.\n");
            httpClientNewError(connection, METHOD_UNKNOWN, 0, 400, 
                               internAtom("Couldn't find end of headers"));
        }
        return 1;
    }
    httpSetTimeout(connection, clientTimeout);
    return 0;
}

int
httpClientRawErrorHeaders(HTTPConnectionPtr connection,
                          int code, AtomPtr message,
                          int close, AtomPtr headers)
{
    int fd = connection->fd;
    int n;
    char *url; int url_len;
    char *etag;

    assert(connection->flags & CONN_WRITER);
    assert(code != 0);

    if(close >= 0) {
        if(connection->request)
            close = 
                close || !(connection->request->flags & REQUEST_PERSISTENT);
        else
            close = 1;
    }
    if(connection->request && connection->request->object) {
        url = connection->request->object->key;
        url_len = connection->request->object->key_size;
        etag = connection->request->object->etag;
    } else {
        url = NULL;
        url_len = 0;
        etag = NULL;
    }

    if(connection->buf == NULL) {
        connection->buf = get_chunk();
        if(connection->buf == NULL) {
            httpClientFinish(connection, 1);
            return 1;
        }
    }

    n = httpWriteErrorHeaders(connection->buf, CHUNK_SIZE, 0,
                              connection->request &&
                              connection->request->method != METHOD_HEAD,
                              code, message, close > 0, headers,
                              url, url_len, etag);
    if(n <= 0) {
        shutdown(connection->fd, 1);
        if(close >= 0)
            httpClientFinish(connection, 1);
        return 1;
    }

    httpSetTimeout(connection, clientTimeout);
    do_stream(IO_WRITE, fd, 0, connection->buf, n, 
              close > 0 ? httpErrorStreamHandler :
              close == 0 ? httpErrorNocloseStreamHandler :
              httpErrorNofinishStreamHandler,
              connection);

    return 1;
}

int
httpClientRawError(HTTPConnectionPtr connection, int code, AtomPtr message,
                   int close)
{
    return httpClientRawErrorHeaders(connection, code, message, close, NULL);
}

int
httpClientNoticeErrorHeaders(HTTPRequestPtr request, int code, AtomPtr message,
                             AtomPtr headers)
{
    if(request->error_message)
        releaseAtom(request->error_message);
    if(request->error_headers)
        releaseAtom(request->error_headers);
    request->error_code = code;
    request->error_message = message;
    request->error_headers = headers;
    httpClientNoticeRequest(request, 0);
    return 1;
}

int
httpClientNoticeError(HTTPRequestPtr request, int code, AtomPtr message)
{
    return httpClientNoticeErrorHeaders(request, code, message, NULL);
}

int
httpClientError(HTTPRequestPtr request, int code, AtomPtr message)
{
    if(request->error_message)
        releaseAtom(request->error_message);
    request->error_code = code;
    request->error_message = message;
    if(request->chandler) {
        abortConditionHandler(request->chandler);
        request->chandler = NULL;
    } else if(request->object)
        notifyObject(request->object);
    return 1;
}

/* This may be called from object handlers. */
int
httpClientLeanError(HTTPRequestPtr request, int code, AtomPtr message)
{
    if(request->error_message)
        releaseAtom(request->error_message);
    request->error_code = code;
    request->error_message = message;
    return 1;
}


int
httpClientNewError(HTTPConnectionPtr connection, int method, int persist,
                   int code, AtomPtr message)
{
    HTTPRequestPtr request;
    request = httpMakeRequest();
    if(request == NULL) {
        do_log(L_ERROR, "Couldn't allocate error request.\n");
        httpClientFinish(connection, 1);
        return 1;
    }
    request->method = method;
    if(persist)
        request->flags |= REQUEST_PERSISTENT;
    else
        request->flags &= ~REQUEST_PERSISTENT;
    request->error_code = code;
    request->error_message = message;

    httpQueueRequest(connection, request);
    httpClientNoticeRequest(request, 0);
    return 1;
}
        
int
httpErrorStreamHandler(int status,
                       FdEventHandlerPtr event,
                       StreamRequestPtr srequest)
{
    HTTPConnectionPtr connection = srequest->data;

    if(status == 0 && !streamRequestDone(srequest))
        return 0;

    httpClientFinish(connection, 1);
    return 1;
}

int
httpErrorNocloseStreamHandler(int status,
                              FdEventHandlerPtr event,
                              StreamRequestPtr srequest)
{
    HTTPConnectionPtr connection = srequest->data;

    if(status == 0 && !streamRequestDone(srequest))
        return 0;

    httpClientFinish(connection, 0);
    return 1;
}

int
httpErrorNofinishStreamHandler(int status,
                               FdEventHandlerPtr event,
                               StreamRequestPtr srequest)
{
    if(status == 0 && !streamRequestDone(srequest))
        return 0;

    return 1;
}

int
httpClientHandlerHeaders(FdEventHandlerPtr event, StreamRequestPtr srequest,
                         HTTPConnectionPtr connection)
{
    HTTPRequestPtr request;
    int rc;
    int method, version;
    AtomPtr url = NULL;
    int start;
    int code;
    AtomPtr message;

    start = 0;
    /* Work around clients working around NCSA lossage. */
    if(connection->reqbuf[0] == '\n')
        start = 1;
    else if(connection->reqbuf[0] == '\r' && connection->reqbuf[1] == '\n')
        start = 2;

    httpSetTimeout(connection, -1);
    rc = httpParseClientFirstLine(connection->reqbuf, start,
                                  &method, &url, &version);
    if(rc <= 0) {
        do_log(L_ERROR, "Couldn't parse client's request line\n");
        code = 400;
        message =  internAtom("Error in request line");
        goto fail;
    }

    do_log(D_CLIENT_REQ, "Client request: ");
    do_log_n(D_CLIENT_REQ, connection->reqbuf, rc - 1);
    do_log(D_CLIENT_REQ, "\n");

    if(version != HTTP_10 && version != HTTP_11) {
        do_log(L_ERROR, "Unknown client HTTP version\n");
        code = 400;
        message = internAtom("Error in first request line");
        goto fail;
    }

    if(method == METHOD_UNKNOWN) {
        code = 501;
        message =  internAtom("Method not implemented");
        goto fail;
    }

    request = httpMakeRequest();
    if(request == NULL) {
        do_log(L_ERROR, "Couldn't allocate client request.\n");
        code = 500;
        message = internAtom("Couldn't allocate client request");
        goto fail;
    }

    if(connection->version != HTTP_UNKNOWN && version != connection->version) {
        do_log(L_WARN, "Client version changed!\n");
    }

    connection->version = version;
    request->flags = REQUEST_PERSISTENT;
    request->method = method;
    request->cache_control = no_cache_control;
    httpQueueRequest(connection, request);
    connection->reqbegin = rc;
    return httpClientRequest(request, url);

 fail:
    if(url) releaseAtom(url);
    shutdown(connection->fd, 0);
    connection->reqlen = 0;
    connection->reqbegin = 0;
    httpConnectionDestroyReqbuf(connection);
    connection->flags &= ~CONN_READER;
    httpClientNewError(connection, METHOD_UNKNOWN, 0, code, message);
    return 1;

}

static int
httpClientRequestDelayed(TimeEventHandlerPtr event)
{
    HTTPRequestPtr request = *(HTTPRequestPtr*)event->data;
    AtomPtr url;
    url = internAtomN(request->object->key, request->object->key_size);
    if(url == NULL) {
        do_log(L_ERROR, "Couldn't allocate url.\n");
        abortObject(request->object, 503, internAtom("Couldn't allocate url"));
        return 1;
    }
    httpClientRequest(request, url);
    return 1;
}

int
delayedHttpClientRequest(HTTPRequestPtr request)
{
    TimeEventHandlerPtr event;
    event = scheduleTimeEvent(-1, httpClientRequestDelayed,
                              sizeof(request), &request);
    if(!event)
        return -1;
    return 1;
}

int
httpClientRequest(HTTPRequestPtr request, AtomPtr url)
{
    HTTPConnectionPtr connection = request->connection;
    int i, rc;
    int body_len, body_te;
    AtomPtr headers;
    CacheControlRec cache_control;
    AtomPtr via, expect, auth;
    HTTPConditionPtr condition;
    HTTPRangeRec range;

    assert(!request->chandler);
    assert(connection->reqbuf);

    i = httpParseHeaders(1, url,
                         connection->reqbuf, connection->reqbegin, request,
                         &headers, &body_len, 
                         &cache_control, &condition, &body_te,
                         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                         &expect, &range, NULL, NULL, &via, &auth);
    if(i < 0) {
        releaseAtom(url);
        do_log(L_ERROR, "Couldn't parse client headers.\n");
        shutdown(connection->fd, 0);
        request->flags &= ~REQUEST_PERSISTENT;
        connection->flags &= ~CONN_READER;
        httpClientNoticeError(request, 503,
                              internAtom("Couldn't parse client headers"));
        return 1;
    }

    connection->reqbegin = i;

    if(body_len < 0) {
        if(request->method == METHOD_GET || request->method == METHOD_HEAD)
            body_len = 0;
    }
    connection->bodylen = body_len;
    connection->reqte = body_te;

    if(authRealm) {
        AtomPtr message = NULL;
        AtomPtr challenge = NULL;
        int code = checkClientAuth(auth, url, &message, &challenge);
        if(auth) {
            releaseAtom(auth);
            auth = NULL;
        }
        if(expect) {
            releaseAtom(expect);
            expect = NULL;
        }
        if(code) {
            request->flags |= REQUEST_FORCE_ERROR;
            httpClientDiscardBody(connection);
            httpClientNoticeErrorHeaders(request, code, message, challenge);
            return 1;
        }
    }

    if(auth) {
        releaseAtom(auth);
        auth = NULL;
    }

    if(expect) {
        if(expect == atom100Continue && REQUEST_SIDE(request)) {
            request->flags |= REQUEST_WAIT_CONTINUE;
        } else {
            httpClientDiscardBody(connection);
            httpClientNoticeError(request, 417,
                                  internAtom("Expectation failed"));
            releaseAtom(expect);
            return 1;
        }
        releaseAtom(expect);
    }

    request->from = range.from < 0 ? 0 : range.from;
    request->to = range.to;
    request->cache_control = cache_control;
    request->via = via;
    request->headers = headers;
    request->condition = condition;
    request->object = NULL;

    if(connection->serviced > 500)
        request->flags &= ~REQUEST_PERSISTENT;

    if(request->method == METHOD_CONNECT) {
        if(connection->flags & CONN_WRITER) {
            /* For now */
            httpClientDiscardBody(connection);
            httpClientNoticeError(request, 500,
                                  internAtom("Pipelined CONNECT "
                                             "not supported"));
            return 1;
        }
        if(connection->flags & CONN_BIGREQBUF) {
            /* For now */
            httpClientDiscardBody(connection);
            httpClientNoticeError(request, 500,
                                  internAtom("CONNECT over big buffer "
                                             "not supported"));
            return 1;
        }
        connection->flags &= ~CONN_READER;
        do_tunnel(connection->fd, connection->reqbuf, 
                  connection->reqbegin, connection->reqlen, url);
        connection->fd = -1;
        connection->reqbuf = NULL;
        connection->reqlen = 0;
        connection->reqbegin = 0;
        httpClientFinish(connection, 2);
        return 1;
    }

    rc = urlForbidden(url, httpClientRequestContinue, request);
    if(rc < 0) {
        do_log(L_ERROR, "Couldn't schedule httpClientRequestContinue.\n");
        httpClientDiscardBody(connection);
        httpClientNoticeError(request, 500,
                              internAtom("Couldn't schedule "
                                         "httpClientRequestContinue"));
        return 1;
    }
    return 1;
}

int
httpClientRequestContinue(int forbidden_code, AtomPtr url,
                          AtomPtr forbidden_message, AtomPtr forbidden_headers,
                          void *closure)
{
    HTTPRequestPtr request = (HTTPRequestPtr)closure;
    HTTPConnectionPtr connection = request->connection;
    RequestFunction requestfn;
    ObjectPtr object = NULL;

    if(forbidden_code < 0) {
        releaseAtom(url);
        httpClientDiscardBody(connection);
        httpClientNoticeError(request, 500, 
                              internAtomError(-forbidden_code,
                                              "Couldn't test for forbidden "
                                              "URL"));
        return 1;
    }

    if(forbidden_code) {
        releaseAtom(url);
        httpClientDiscardBody(connection);
        httpClientNoticeErrorHeaders(request,
                                     forbidden_code, forbidden_message,
                                     forbidden_headers);
        return 1;
    }

    requestfn = 
        urlIsLocal(url->string, url->length) ? 
        httpLocalRequest :
        httpServerRequest;

    if(request->method == METHOD_POST || request->method == METHOD_PUT) {
        do {
            object = findObject(OBJECT_HTTP, url->string, url->length);
            if(object) {
                privatiseObject(object, 0);
                releaseObject(object);
            }
        } while(object);
        request->object = makeObject(OBJECT_HTTP, url->string, url->length,
                                     0, 0, requestfn, NULL);
        if(request->object == NULL) {
            httpClientDiscardBody(connection);
            httpClientNoticeError(request, 503,
                                  internAtom("Couldn't allocate object"));
            return 1;
        }
        if(requestfn == httpLocalRequest)
            request->object->flags |= OBJECT_LOCAL;
        return httpClientSideRequest(request);
    }

    if(request->cache_control.flags & CACHE_AUTHORIZATION) {
        do {
            object = makeObject(OBJECT_HTTP, url->string, url->length, 0, 0,
                                requestfn, NULL);
            if(object && object->flags != OBJECT_INITIAL) {
                if(!(object->cache_control & CACHE_PUBLIC)) {
                    privatiseObject(object, 0);
                    releaseObject(object);
                    object = NULL;
                } else
                    break;
            }
        } while(object == NULL);
        if(object)
            object->flags |= OBJECT_LINEAR;
    } else {
        object = findObject(OBJECT_HTTP, url->string, url->length);
        if(!object)
            object = makeObject(OBJECT_HTTP, url->string, url->length, 1, 1,
                                requestfn, NULL);
    }
    releaseAtom(url);
    url = NULL;

    if(!object) {
        do_log(L_ERROR, "Couldn't allocate object.\n");
        httpClientDiscardBody(connection);
        httpClientNoticeError(request, 503,
                              internAtom("Couldn't allocate object"));
        return 1;
    }

    if(object->request == httpLocalRequest) {
        object->flags |= OBJECT_LOCAL;
    } else {
        if(disableProxy) {
            httpClientDiscardBody(connection);
            httpClientNoticeError(request, 403,
                                  internAtom("Proxying disabled"));
            releaseObject(object);
            return 1;
        }

        if(!checkVia(proxyName, request->via)) {
            httpClientDiscardBody(connection);
            httpClientNoticeError(request, 504, 
                                  internAtom("Proxy loop detected"));
            releaseObject(object);
            return 1;
        }
    }

    request->object = object;

    httpClientDiscardBody(connection);
    httpClientNoticeRequest(request, 0);
    return 1;
}

static int httpClientDelayed(TimeEventHandlerPtr handler);

int
httpClientDiscardBody(HTTPConnectionPtr connection)
{
    TimeEventHandlerPtr handler;

    assert(connection->reqoffset == 0);
    assert(connection->flags & CONN_READER);

    if(connection->reqte != TE_IDENTITY)
        goto fail;

    if(connection->bodylen < 0)
        goto fail;

    if(connection->bodylen < connection->reqlen - connection->reqbegin) {
        connection->reqbegin += connection->bodylen;
        connection->bodylen = 0;
    } else {
        connection->bodylen -= connection->reqlen - connection->reqbegin;
        connection->reqbegin = 0;
        connection->reqlen = 0;
        httpConnectionDestroyReqbuf(connection);
    }
    connection->reqte = TE_UNKNOWN;

    if(connection->bodylen > 0) {
        httpSetTimeout(connection, clientTimeout);
        do_stream_buf(IO_READ | IO_NOTNOW,
                      connection->fd, connection->reqlen,
                      &connection->reqbuf, CHUNK_SIZE,
                      httpClientDiscardHandler, connection);
        return 1;
    }

    if(connection->reqlen > connection->reqbegin &&
       (connection->reqlen - connection->reqbegin) > 0) {
        memmove(connection->reqbuf, connection->reqbuf + connection->reqbegin,
                connection->reqlen - connection->reqbegin);
        connection->reqlen -= connection->reqbegin;
        connection->reqbegin = 0;
    } else {
        connection->reqlen = 0;
        connection->reqbegin = 0;
    }

    httpSetTimeout(connection, clientTimeout);
    /* We need to delay in order to make sure the previous request
       gets queued on the server side.  IO_NOTNOW isn't strong enough
       for that due to IO_IMMEDIATE. */
    handler = scheduleTimeEvent(-1, httpClientDelayed,
                                sizeof(connection), &connection);
    if(handler == NULL) {
        do_log(L_ERROR, "Couldn't schedule reading from client.");
        goto fail;
    }
    return 1;

 fail:
    connection->reqlen = 0;
    connection->reqbegin = 0;
    connection->bodylen = 0;
    connection->reqte = TE_UNKNOWN;
    shutdown(connection->fd, 2);
    handler = scheduleTimeEvent(-1, httpClientDelayed,
                                sizeof(connection), &connection);
    if(handler == NULL) {
        do_log(L_ERROR, "Couldn't schedule reading from client.");
        connection->flags &= ~CONN_READER;
    }
    return 1;
}

static int
httpClientDelayed(TimeEventHandlerPtr event)
{
     HTTPConnectionPtr connection = *(HTTPConnectionPtr*)event->data;

     /* IO_NOTNOW is unfortunate, but needed to avoid starvation if a
        client is pipelining a lot of requests. */
     if(connection->reqlen > 0) {
         int bufsize;
         if((connection->flags & CONN_BIGREQBUF) &&
            connection->reqlen < CHUNK_SIZE)
             httpConnectionUnbigifyReqbuf(connection);
         /* Don't read new requests if buffer is big. */
         bufsize = (connection->flags & CONN_BIGREQBUF) ?
             connection->reqlen : CHUNK_SIZE;
         do_stream(IO_READ | IO_IMMEDIATE | IO_NOTNOW,
                   connection->fd, connection->reqlen,
                   connection->reqbuf, bufsize,
                   httpClientHandler, connection);
     } else {
         httpConnectionDestroyReqbuf(connection);
         do_stream_buf(IO_READ | IO_NOTNOW,
                       connection->fd, 0,
                       &connection->reqbuf, CHUNK_SIZE,
                       httpClientHandler, connection);
     }
     return 1;
}

int
httpClientDiscardHandler(int status,
                         FdEventHandlerPtr event, StreamRequestPtr request)
{
    HTTPConnectionPtr connection = request->data;

    assert(connection->flags & CONN_READER);
    if(status) {
        if(status < 0 && status != -EPIPE)
            do_log_error(L_ERROR, -status, "Couldn't read from client");
        connection->bodylen = -1;
        return httpClientDiscardBody(connection);
    }

    assert(request->offset > connection->reqlen);
    connection->reqlen = request->offset;

    httpClientDiscardBody(connection);
    return 1;
}

int
httpClientNoticeRequest(HTTPRequestPtr request, int novalidate)
{
    HTTPConnectionPtr connection = request->connection;
    ObjectPtr object = request->object;
    int serveNow = (request == connection->request);
    int validate = 0;
    int conditional = 0;
    int local, haveData;
    int rc;

    assert(!request->chandler);

    if(request->error_code) {
        if((request->flags & REQUEST_FORCE_ERROR) || REQUEST_SIDE(request) ||
           request->object == NULL ||
           (request->object->flags & OBJECT_LOCAL) ||
           (request->object->flags & OBJECT_ABORTED) ||
           (relaxTransparency < 1 && !proxyOffline)) {
            if(serveNow) {
                connection->flags |= CONN_WRITER;
                return httpClientRawErrorHeaders(connection,
                                                 request->error_code, 
                                                 retainAtom(request->
                                                            error_message),
                                                 0, request->error_headers);
            } else {
                return 1;
            }
        }
    }

    if(REQUEST_SIDE(request)) {
        assert(!(request->flags & REQUEST_REQUESTED));
        if(serveNow) {
            assert(!request->chandler);
            request->chandler =
                conditionWait(&request->object->condition, 
                              httpClientGetHandler,
                              sizeof(request), &request);
            if(request->chandler == NULL) {
                do_log(L_ERROR, "Couldn't register condition handler.\n");
                connection->flags |= CONN_WRITER;
                httpClientRawError(connection, 500,
                                   internAtom("Couldn't register "
                                              "condition handler"),
                                   0);
                return 1;
            }
            connection->flags |= CONN_WRITER;
            rc = object->request(request->object,
                                 request->method,
                                 request->from, request->to, 
                                 request,
                                 request->object->request_closure);
        }
        return 1;
    }

    local = urlIsLocal(object->key, object->key_size);
    objectFillFromDisk(object, request->from,
                       request->method == METHOD_HEAD ? 0 : 1);

    if(request->condition && request->condition->ifrange) {
        if(!object->etag || 
           strcmp(object->etag, request->condition->ifrange) != 0) {
            request->from = 0;
            request->to = -1;
        }
    }

    if(object->flags & OBJECT_DYNAMIC) {
        request->from = 0;
        request->to = -1;
    }

    if(request->method == METHOD_HEAD ||
       request->object->code == 204 ||
       request->object->code < 200)
        haveData = !(request->object->flags & OBJECT_INITIAL);
    else
        haveData = 
            (request->object->length >= 0 && 
             request->object->length <= request->from) ||
            (objectHoleSize(request->object, request->from) == 0);

    if(request->flags & REQUEST_REQUESTED)
        validate = 0;
    else if(novalidate || (!local && proxyOffline))
        validate = 0;
    else if(local)
        validate = 
            objectMustRevalidate(request->object, &request->cache_control);
    else if(request->cache_control.flags & CACHE_ONLY_IF_CACHED)
        validate = 0;
    else if((request->object->flags & OBJECT_FAILED) &&
            !(object->flags & OBJECT_INPROGRESS) &&
            !relaxTransparency)
        validate = 1;
    else if(request->method != METHOD_HEAD &&
            !objectHasData(object, request->from, request->to) &&
            !(object->flags & OBJECT_INPROGRESS))
        validate = 1;
    else if(objectMustRevalidate((relaxTransparency <= 1 ? 
                                  request->object : NULL),
                                 &request->cache_control))
        validate = 1;
    else
        validate = 0;

    if(request->cache_control.flags & CACHE_ONLY_IF_CACHED) {
        validate = 0;
        if(!haveData) {
            if(serveNow) {
                connection->flags |= CONN_WRITER;
                return httpClientRawError(connection, 504,
                                          internAtom("Object not in cache"),
                                          0);
            } else
                return 1;
        }
    }

    if(!(request->object->flags & OBJECT_VALIDATING) &&
       ((!validate && haveData) ||
        (request->object->flags & OBJECT_FAILED))) {
        if(serveNow) {
            connection->flags |= CONN_WRITER;
            lockChunk(request->object, request->from / CHUNK_SIZE);
            return httpServeObject(connection);
        } else {
            return 1;
        }
    }

    if((request->flags & REQUEST_REQUESTED) &&
       !(request->object->flags & OBJECT_INPROGRESS)) {
        /* This can happen either because the server side ran out of
           memory, or because it is using HEAD validation.  We mark
           the object to be fetched again. */
        request->flags &= ~REQUEST_REQUESTED;
    }

    if(serveNow) {
        connection->flags |= CONN_WRITER;
        if(!local && proxyOffline)
            return httpClientRawError(connection, 502, 
                                      internAtom("Disconnected operation "
                                                 "and object not in cache"),
                                      0);
        request->chandler =
            conditionWait(&request->object->condition, httpClientGetHandler, 
                          sizeof(request), &request);
        if(request->chandler == NULL) {
            do_log(L_ERROR, "Couldn't register condition handler.\n");
            return httpClientRawError(connection, 503,
                                      internAtom("Couldn't register "
                                                 "condition handler"), 0);
        }
    }

    if(request->object->flags & OBJECT_VALIDATING)
        return 1;

    conditional = (haveData && request->method == METHOD_GET);
    if(!mindlesslyCacheVary && (request->object->cache_control & CACHE_VARY))
        conditional = conditional && (request->object->etag != NULL);

    conditional =
        conditional && !(request->object->cache_control & CACHE_MISMATCH);

    request->object->flags |= OBJECT_VALIDATING;
    rc = request->object->request(request->object,
                                  conditional ? METHOD_CONDITIONAL_GET : 
                                  request->method,
                                  request->from, request->to, request,
                                  request->object->request_closure);
    if(rc < 0) {
        if(request->chandler)
            unregisterConditionHandler(request->chandler);
        request->chandler = NULL;
        request->object->flags &= ~OBJECT_VALIDATING;
        request->object->flags |= OBJECT_FAILED;
        if(request->error_message)
            releaseAtom(request->error_message);
        request->error_code = 503;
        request->error_message = internAtom("Couldn't schedule get");
    }
    return 1;
}

static int
httpClientNoticeRequestDelayed(TimeEventHandlerPtr event)
{
    HTTPRequestPtr request = *(HTTPRequestPtr*)event->data;
    httpClientNoticeRequest(request, 0);
    return 1;
}

int
delayedHttpClientNoticeRequest(HTTPRequestPtr request)
{
    TimeEventHandlerPtr event;
    event = scheduleTimeEvent(-1, httpClientNoticeRequestDelayed,
                              sizeof(request), &request);
    if(!event)
        return -1;
    return 1;
}

int
httpClientContinueDelayed(TimeEventHandlerPtr event)
{
    static char httpContinue[] = "HTTP/1.1 100 Continue\r\n\r\n";
    HTTPConnectionPtr connection = *(HTTPConnectionPtr*)event->data;

    do_stream(IO_WRITE, connection->fd, 0, httpContinue, 25,
              httpErrorNofinishStreamHandler, connection);
    return 1;
}

int
delayedHttpClientContinue(HTTPConnectionPtr connection)
{
    TimeEventHandlerPtr event;
    event = scheduleTimeEvent(-1, httpClientContinueDelayed,
                              sizeof(connection), &connection);
    if(!event)
        return -1;
    return 1;
}

int
httpClientGetHandler(int status, ConditionHandlerPtr chandler)
{
    HTTPRequestPtr request = *(HTTPRequestPtr*)chandler->data;
    HTTPConnectionPtr connection = request->connection;
    ObjectPtr object = request->object;
    int rc;

    assert(request == connection->request);

    if(request->request) {
        assert(request->object->flags & OBJECT_INPROGRESS);
        assert(!request->request->object ||
               request->request->object == request->object);
    }

    if(status < 0) {
        object->flags &= ~OBJECT_VALIDATING; /* for now */
        if(request->request && request->request->request == request)
            httpServerClientReset(request->request);
        lockChunk(object, request->from / CHUNK_SIZE);
        request->chandler = NULL;
        rc = delayedHttpServeObject(connection);
        if(rc < 0) {
            unlockChunk(object, request->from / CHUNK_SIZE);
            do_log(L_ERROR, "Couldn't schedule serving.\n");
            abortObject(object, 503, internAtom("Couldn't schedule serving"));
        }
        return 1;
    }

    if(object->flags & OBJECT_VALIDATING)
        return 0;

    if(request->error_code) {
        lockChunk(object, request->from / CHUNK_SIZE);
        request->chandler = NULL;
        rc = delayedHttpServeObject(connection);
        if(rc < 0) {
            unlockChunk(object, request->from / CHUNK_SIZE);
            do_log(L_ERROR, "Couldn't schedule serving.\n");
            abortObject(object, 503, internAtom("Couldn't schedule serving"));
        }
        return 1;
    }

    if(request->flags & REQUEST_WAIT_CONTINUE) {
        request->flags &= ~REQUEST_WAIT_CONTINUE;
        if(object->code == 100 && request->request &&
           !(request->request->flags & REQUEST_WAIT_CONTINUE))
            delayedHttpClientContinue(connection);
        return 0;
    }

    /* See httpServerHandlerHeaders */
    if((object->flags & OBJECT_SUPERSEDED) &&
       request->request && request->request->can_mutate) {
        ObjectPtr new_object = retainObject(request->request->can_mutate);
        if(object->requestor == request) {
            if(new_object->requestor == NULL)
                new_object->requestor = request;
            object->requestor = NULL;
        }
        request->chandler = NULL;
        releaseObject(object);
        request->object = new_object;
        request->request->object = new_object;
        /* We're handling the wrong object now.  It's simpler to
           rebuild the whole data structure from scratch rather than
           trying to compensate. */
        rc = delayedHttpClientNoticeRequest(request);
        if(rc < 0) {
            do_log(L_ERROR, "Couldn't schedule noticing of request.");
            abortObject(object, 500,
                        internAtom("Couldn't schedule "
                                   "noticing of request"));
            /* We're probably out of memory.  What can we do? */
            shutdown(connection->fd, 1);
        }
        return 1;
    }

    if(object->requestor != request && !(object->flags & OBJECT_ABORTED)) {
        /* Make sure we don't serve an object that is stale for us
           unless we're the requestor. */
        if((object->flags & (OBJECT_LINEAR | OBJECT_MUTATING)) ||
           objectMustRevalidate(object, &request->cache_control)) {
           if(object->flags & OBJECT_INPROGRESS)
               return 0;
           rc = delayedHttpClientNoticeRequest(request);
           if(rc < 0) {
               do_log(L_ERROR, "Couldn't schedule noticing of request.");
               abortObject(object, 500,
                           internAtom("Couldn't schedule "
                                      "noticing of request"));
           } else {
               request->chandler = NULL;
               return 1;
           }
        }
    }

    if(object->flags & (OBJECT_INITIAL | OBJECT_VALIDATING)) {
        if(object->flags & (OBJECT_INPROGRESS | OBJECT_VALIDATING)) {
            return 0;
        } else if(object->flags & OBJECT_FAILED) {
            if(request->error_code)
                abortObject(object, 
                            request->error_code, 
                            retainAtom(request->error_message));
            else {
                abortObject(object, 500,
                            internAtom("Error message lost in transit"));
            }
        } else {
            /* The request was pruned by httpServerDiscardRequests */
            if(chandler == request->chandler) {
                int rc;
                request->chandler = NULL;
                rc = delayedHttpClientNoticeRequest(request);
                if(rc < 0)
                    abortObject(object, 500,
                                internAtom("Couldn't allocate "
                                           "delayed notice request"));
                else
                    return 1;
            } else {
                abortObject(object, 500,
                            internAtom("Wrong request pruned -- "
                                       "this shouldn't happen"));
            }
        }
    }

    if(request->object->flags & OBJECT_DYNAMIC) {
        if(objectHoleSize(request->object, 0) == 0) {
            request->from = 0;
            request->to = -1;
        } else {
            /* We really should request again if that is not the case */
        }
    }

    lockChunk(object, request->from / CHUNK_SIZE);
    request->chandler = NULL;
    rc = delayedHttpServeObject(connection);
    if(rc < 0) {
        unlockChunk(object, request->from / CHUNK_SIZE);
        do_log(L_ERROR, "Couldn't schedule serving.\n");
        abortObject(object, 503, internAtom("Couldn't schedule serving"));
    }
    return 1;
}

int
httpClientSideRequest(HTTPRequestPtr request)
{
    HTTPConnectionPtr connection = request->connection;

    if(request->from < 0 || request->to >= 0) {
        httpClientNoticeError(request, 501,
                              internAtom("Partial requests not implemented"));
        httpClientDiscardBody(connection);
        return 1;
    }
    if(connection->reqte != TE_IDENTITY) {
        httpClientNoticeError(request, 501,
                              internAtom("Chunked requests not implemented"));
        httpClientDiscardBody(connection);
        return 1;
    }
    if(connection->bodylen < 0) {
        httpClientNoticeError(request, 502,
                              internAtom("POST or PUT without "
                                         "Content-Length"));
        httpClientDiscardBody(connection);
        return 1;
    }
    if(connection->reqlen < 0) {
        httpClientNoticeError(request, 502,
                              internAtom("Incomplete POST or PUT"));
        httpClientDiscardBody(connection);
        return 1;
    }
        
    return httpClientNoticeRequest(request, 0);
}

int 
httpClientSideHandler(int status,
                      FdEventHandlerPtr event,
                      StreamRequestPtr srequest)
{
    HTTPConnectionPtr connection = srequest->data;
    HTTPRequestPtr request = connection->request;
    HTTPRequestPtr requestee;
    HTTPConnectionPtr server;
    int push;
    int code;
    AtomPtr message = NULL;

    assert(connection->flags & CONN_SIDE_READER);

    if((request->object->flags & OBJECT_ABORTED) || 
       !(request->object->flags & OBJECT_INPROGRESS)) {
        code = request->object->code;
        message = retainAtom(request->object->message);
        goto fail;
    }
        
    if(status < 0) {
        do_log_error(L_ERROR, -status, "Reading from client");
        code = 502;
        message = internAtomError(-status, "Couldn't read from client");
        goto fail;
    }

    requestee = request->request;
    server = requestee->connection;

    push = MIN(srequest->offset - connection->reqlen, 
               connection->bodylen - connection->reqoffset);
    if(push > 0) {
        connection->reqlen += push;
        httpServerDoSide(server);
        return 1;
    }

    if(server->reqoffset >= connection->bodylen) {
        connection->flags &= ~(CONN_READER | CONN_SIDE_READER);
        return 1;
    }

    assert(status);
    do_log(L_ERROR, "Incomplete client request.\n");
    code = 502;
    message = internAtom("Incomplete client request");

 fail:
    request->error_code = code;
    if(request->error_message)
        releaseAtom(request->error_message);
    request->error_message = message;
    if(request->error_headers)
        releaseAtom(request->error_headers);
    request->error_headers = NULL;

    if(request->request) {
        shutdown(request->request->connection->fd, 2);
        pokeFdEvent(request->request->connection->fd, -ESHUTDOWN, POLLOUT);
    }
    notifyObject(request->object);
    connection->flags &= ~CONN_SIDE_READER;
    httpClientDiscardBody(connection);
    return 1;
}

int 
httpServeObject(HTTPConnectionPtr connection)
{
    HTTPRequestPtr request = connection->request;
    ObjectPtr object = request->object;
    int i = request->from / CHUNK_SIZE;
    int j = request->from % CHUNK_SIZE;
    int n, len, rc;
    int bufsize = CHUNK_SIZE;
    int condition_result;

    object->atime = current_time.tv_sec;
    objectMetadataChanged(object, 0);

    httpSetTimeout(connection, -1);

    if((request->error_code && relaxTransparency <= 0) ||
       object->flags & OBJECT_INITIAL) {
        object->flags &= ~OBJECT_FAILED;
        unlockChunk(object, i);
        if(request->error_code)
            return httpClientRawError(connection,
                                      request->error_code, 
                                      retainAtom(request->error_message), 0);
        else
            return httpClientRawError(connection,
                                      500, internAtom("Object vanished."), 0);
    }

    if(!(object->flags & OBJECT_INPROGRESS) && object->code == 0) {
        if(object->flags & OBJECT_INITIAL) {
            unlockChunk(object, i);
            return httpClientRawError(connection, 503,
                                      internAtom("Error message lost"), 0);
                                      
        } else {
            unlockChunk(object, i);
            do_log(L_ERROR, "Internal proxy error: object has no code.\n");
            return httpClientRawError(connection, 500,
                                      internAtom("Internal proxy error: "
                                                 "object has no code"), 0);
        }
    }

    condition_result = httpCondition(object, request->condition);

    if(condition_result == CONDITION_FAILED) {
        unlockChunk(object, i);
        return httpClientRawError(connection, 412,
                                  internAtom("Precondition failed"), 0);
    } else if(condition_result == CONDITION_NOT_MODIFIED) {
        unlockChunk(object, i);
        return httpClientRawError(connection, 304,
                                  internAtom("Not modified"), 0);
    }

    objectFillFromDisk(object, request->from,
                       (request->method == METHOD_HEAD ||
                        condition_result != CONDITION_MATCH) ? 0 : 1);

    if(((object->flags & OBJECT_LINEAR) &&
        (object->requestor != connection->request)) ||
       ((object->flags & OBJECT_SUPERSEDED) &&
        !(object->flags & OBJECT_LINEAR))) {
        if(request->request) {
            request->request->request = NULL;
            request->request = NULL;
            request->object->requestor = NULL;
        }
        object = makeObject(OBJECT_HTTP,
                            object->key, object->key_size, 1, 0,
                            object->request, NULL);
        if(request->object->requestor == request)
            request->object->requestor = NULL;
        unlockChunk(request->object, i);
        releaseObject(request->object);
        request->object = NULL;
        if(object == NULL) {
            do_log(L_ERROR, "Couldn't allocate object.");
            return httpClientRawError(connection, 501,
                                      internAtom("Couldn't allocate object"), 
                                      1);
        }
        if(urlIsLocal(object->key, object->key_size)) {
            object->flags |= OBJECT_LOCAL;
            object->request = httpLocalRequest;
        }
        request->object = object;
        connection->flags &= ~CONN_WRITER;
        return httpClientNoticeRequest(request, 1);
    }

    if(object->flags & OBJECT_ABORTED) {
        unlockChunk(object, i);
        return httpClientNoticeError(request, object->code, 
                                     retainAtom(object->message));
    }

    if(connection->buf == NULL)
        connection->buf = get_chunk();
    if(connection->buf == NULL) {
        unlockChunk(object, i);
        do_log(L_ERROR, "Couldn't allocate client buffer.\n");
        connection->flags &= ~CONN_WRITER;
        httpClientFinish(connection, 1);
        return 1;
    }

    if(object->length >= 0 && request->to >= object->length)
        request->to = -1;

    if(request->from > 0 || request->to >= 0) {
        if(request->method == METHOD_HEAD) {
            request->to = request->from;
        } else if(request->to < 0) {
            if(object->length >= 0)
                request->to = object->length;
        }
    }

 again:

    connection->len = 0;

    if((request->from <= 0 && request->to < 0) || 
       request->method == METHOD_HEAD) {
        n = snnprintf(connection->buf, 0, bufsize,
                      "HTTP/1.1 %d %s",
                      object->code, atomString(object->message));
    } else {
        if(request->from > request->to) {
            unlockChunk(object, i);
            return httpClientRawError(connection, 416,
                                      internAtom("Requested range "
                                                 "not satisfiable"),
                                      0);
        } else {
            n = snnprintf(connection->buf, 0, bufsize,
                          "HTTP/1.1 206 Partial content");
        }
    }

    n = httpWriteObjectHeaders(connection->buf, n, bufsize,
                               object, request->from, request->to);
    if(n < 0)
        goto fail;

    if(request->method != METHOD_HEAD && 
       condition_result != CONDITION_NOT_MODIFIED &&
       request->to < 0 && object->length < 0) {
        if(connection->version == HTTP_11) {
            connection->te = TE_CHUNKED;
            n = snnprintf(connection->buf, n, bufsize,
                          "\r\nTransfer-Encoding: chunked");
        } else {
            request->flags &= ~REQUEST_PERSISTENT;
        }
    }
        
    if(object->age < current_time.tv_sec) {
        n = snnprintf(connection->buf, n, bufsize,
                      "\r\nAge: %d",
                      (int)(current_time.tv_sec - object->age));
    }
    n = snnprintf(connection->buf, n, bufsize,
                  "\r\nConnection: %s",
                  (request->flags & REQUEST_PERSISTENT) ? 
                  "keep-alive" : "close");

    if(!(object->flags & OBJECT_LOCAL)) {
        if((object->flags & OBJECT_FAILED) && !proxyOffline) {
            n = snnprintf(connection->buf, n, bufsize,
                          "\r\nWarning: 111 %s:%d Revalidation failed",
                          proxyName->string, proxyPort);
            if(request->error_code)
                n = snnprintf(connection->buf, n, bufsize,
                              " (%d %s)",
                              request->error_code, 
                              atomString(request->error_message));
            object->flags &= ~OBJECT_FAILED;
        } else if(proxyOffline && 
                  objectMustRevalidate(object, &request->cache_control)) {
            n = snnprintf(connection->buf, n, bufsize,
                          "\r\nWarning: 112 %s:%d Disconnected operation",
                          proxyName->string, proxyPort);
        } else if(objectIsStale(object, &request->cache_control)) {
            n = snnprintf(connection->buf, n, bufsize,
                          "\r\nWarning: 110 %s:%d Object is stale",
                          proxyName->string, proxyPort);
        } else if(object->expires < 0 && object->max_age < 0 &&
                  object->age < current_time.tv_sec - 24 * 3600) {
            n = snnprintf(connection->buf, n, bufsize,
                          "\r\nWarning: 113 %s:%d Heuristic expiration",
                          proxyName->string, proxyPort);
        }
    }

    n = snnprintf(connection->buf, n, bufsize, "\r\n\r\n");
    
    if(n < 0)
        goto fail;
    
    connection->offset = request->from;

    if(request->method == METHOD_HEAD || 
       condition_result == CONDITION_NOT_MODIFIED ||
       (object->flags & OBJECT_ABORTED)) {
        len = 0;
    } else {
        if(i < object->numchunks) {
            if(object->chunks[i].size <= j)
                len = 0;
            else
                len = object->chunks[i].size - j;
        } else {
            len = 0;
        }
        if(request->to >= 0)
            len = MIN(len, request->to - request->from);
    }

    connection->offset = request->from;
    httpSetTimeout(connection, clientTimeout);
    do_log(D_CLIENT_DATA, "Serving on 0x%lx for 0x%lx: offset %d len %d\n",
           (unsigned long)connection, (unsigned long)object,
           connection->offset, len);
    do_stream_h(IO_WRITE |
                (connection->te == TE_CHUNKED && len > 0 ? IO_CHUNKED : 0),
                connection->fd, 0, 
                connection->buf, n,
                object->chunks[i].data + j, len,
                httpServeObjectStreamHandler, connection);
    return 1;

 fail:
    rc = 0;
    connection->len = 0;
    if(!(connection->flags & CONN_BIGBUF))
        rc = httpConnectionBigify(connection);
    if(rc > 0) {
        bufsize = bigBufferSize;
        goto again;
    }
    unlockChunk(object, i);
    return httpClientRawError(connection, 500,
                              rc == 0 ?
                              internAtom("No space for headers") :
                              internAtom("Couldn't allocate big buffer"), 0);
}

static int
httpServeObjectDelayed(TimeEventHandlerPtr event)
{
    HTTPConnectionPtr connection = *(HTTPConnectionPtr*)event->data;
    httpServeObject(connection);
    return 1;
}

int
delayedHttpServeObject(HTTPConnectionPtr connection)
{
    TimeEventHandlerPtr event;

    assert(connection->request->object->chunks[connection->request->from / 
                                               CHUNK_SIZE].locked > 0);

    event = scheduleTimeEvent(-1, httpServeObjectDelayed,
                              sizeof(connection), &connection);
    if(!event) return -1;
    return 1;
}

static int
httpServeObjectFinishHandler(int status,
                             FdEventHandlerPtr event,
                             StreamRequestPtr srequest)
{
    HTTPConnectionPtr connection = srequest->data;
    HTTPRequestPtr request = connection->request;

    (void)request;
    assert(!request->chandler);

    if(status == 0 && !streamRequestDone(srequest))
        return 0;

    httpSetTimeout(connection, -1);

    if(status < 0) {
        do_log(L_ERROR, "Couldn't terminate chunked reply\n");
        httpClientFinish(connection, 1);
    } else {
        httpClientFinish(connection, 0);
    }
    return 1;
}

int
httpServeChunk(HTTPConnectionPtr connection)
{
    HTTPRequestPtr request = connection->request;
    ObjectPtr object = request->object;
    int i = connection->offset / CHUNK_SIZE;
    int j = connection->offset - (i * CHUNK_SIZE);
    int to, len, len2, end;
    int rc;

    if(object->flags & OBJECT_ABORTED)
        goto fail_no_unlock;

    if(object->length >= 0 && request->to >= 0)
        to = MIN(request->to, object->length);
    else if(object->length >= 0)
        to = object->length;
    else if(request->to >= 0)
        to = request->to;
    else
        to = -1;

    lockChunk(object, i);
    len = 0;
    if(i < object->numchunks)
        len = object->chunks[i].size - j;

    if(request->method != METHOD_HEAD && 
       len < CHUNK_SIZE && connection->offset + len < to) {
        objectFillFromDisk(object, connection->offset + len, 2);
        len = object->chunks[i].size - j;
    }

    if(to >= 0)
        len = MIN(len, to - connection->offset);

    if(len <= 0) {
        if(to >= 0 && connection->offset >= to) {
            if(request->chandler) {
                unregisterConditionHandler(request->chandler);
                request->chandler = NULL;
            }
            unlockChunk(object, i);
            if(connection->te == TE_CHUNKED) {
                httpSetTimeout(connection, clientTimeout);
                do_stream(IO_WRITE | IO_CHUNKED | IO_END,
                          connection->fd, 0, NULL, 0,
                          httpServeObjectFinishHandler, connection);
            } else {
                httpClientFinish(connection,
                                 !(object->length >= 0 &&
                                   connection->offset >= object->length));
            }
            return 1;
        } else {
            if(!request->chandler) {
                request->chandler =
                    conditionWait(&object->condition, 
                                  httpServeObjectHandler,
                                  sizeof(connection), &connection);
                if(!request->chandler) {
                    do_log(L_ERROR, "Couldn't register condition handler\n");
                    goto fail;
                }
            }
            if(!(object->flags & OBJECT_INPROGRESS)) {
                if(object->flags & OBJECT_SUPERSEDED) {
                    goto fail;
                }
                if(REQUEST_SIDE(request)) goto fail;
                rc = object->request(object, request->method,
                                     connection->offset, -1, request,
                                     object->request_closure);
                if(rc <= 0) goto fail;
            }
            return 1;
        }
    } else {
        /* len > 0 */
        if(request->method != METHOD_HEAD)
            objectFillFromDisk(object, (i + 1) * CHUNK_SIZE, 1);
        if(request->chandler) {
            unregisterConditionHandler(request->chandler);
            request->chandler = NULL;
        }
        len2 = 0;
        if(j + len == CHUNK_SIZE && object->numchunks > i + 1) {
            len2 = object->chunks[i + 1].size;
            if(to >= 0)
                len2 = MIN(len2, to - (i + 1) * CHUNK_SIZE);
        }
        /* Lock early -- httpServerRequest may get_chunk */
        if(len2 > 0)
            lockChunk(object, i + 1);
        if(object->length >= 0 && 
           connection->offset + len + len2 == object->length)
            end = 1;
        else
            end = 0;
        /* Prefetch */
        if(!(object->flags & OBJECT_INPROGRESS) && !REQUEST_SIDE(request)) {
            if(object->chunks[i].size < CHUNK_SIZE &&
               to >= 0 && connection->offset + len + 1 < to)
                object->request(object, request->method,
                                connection->offset + len, -1, request,
                                object->request_closure);
            else if(i + 1 < object->numchunks &&
                    object->chunks[i + 1].size == 0 &&
                    to >= 0 && (i + 1) * CHUNK_SIZE + 1 < to)
                object->request(object, request->method,
                                (i + 1) * CHUNK_SIZE, -1, request,
                                object->request_closure);
        }
        if(len2 == 0) {
            httpSetTimeout(connection, clientTimeout);
            do_log(D_CLIENT_DATA, 
                   "Serving on 0x%lx for 0x%lx: offset %d len %d\n",
                   (unsigned long)connection, (unsigned long)object,
                   connection->offset, len);
            /* IO_NOTNOW in order to give other clients a chance to run. */
            do_stream(IO_WRITE | IO_NOTNOW |
                      (connection->te == TE_CHUNKED ? IO_CHUNKED : 0) |
                      (end ? IO_END : 0),
                      connection->fd, 0, 
                      object->chunks[i].data + j, len,
                      httpServeObjectStreamHandler, connection);
        } else {
            httpSetTimeout(connection, clientTimeout);
            do_log(D_CLIENT_DATA, 
                   "Serving on 0x%lx for 0x%lx: offset %d len %d + %d\n",
                   (unsigned long)connection, (unsigned long)object,
                   connection->offset, len, len2);
            do_stream_2(IO_WRITE | IO_NOTNOW |
                        (connection->te == TE_CHUNKED ? IO_CHUNKED : 0) |
                        (end ? IO_END : 0),
                        connection->fd, 0, 
                        object->chunks[i].data + j, len,
                        object->chunks[i + 1].data, len2,
                        httpServeObjectStreamHandler2, connection);
        }            
        return 1;
    }

    abort();

 fail:
    unlockChunk(object, i);
 fail_no_unlock:
    if(request->chandler)
        unregisterConditionHandler(request->chandler);
    request->chandler = NULL;
    httpClientFinish(connection, 1);
    return 1;
}

static int
httpServeChunkDelayed(TimeEventHandlerPtr event)
{
    HTTPConnectionPtr connection = *(HTTPConnectionPtr*)event->data;
    httpServeChunk(connection);
    return 1;
}

int
delayedHttpServeChunk(HTTPConnectionPtr connection)
{
    TimeEventHandlerPtr event;
    event = scheduleTimeEvent(-1, httpServeChunkDelayed,
                              sizeof(connection), &connection);
    if(!event) return -1;
    return 1;
}

int
httpServeObjectHandler(int status, ConditionHandlerPtr chandler)
{
    HTTPConnectionPtr connection = *(HTTPConnectionPtr*)chandler->data;
    HTTPRequestPtr request = connection->request;
    int rc;

    unlockChunk(request->object, connection->offset / CHUNK_SIZE);

    if((request->object->flags & OBJECT_ABORTED) || status < 0) {
        shutdown(connection->fd, 1);
        httpSetTimeout(connection, 10);
        /* httpServeChunk will take care of the error. */
    }

    httpSetTimeout(connection, -1);

    request->chandler = NULL;
    rc = delayedHttpServeChunk(connection);
    if(rc < 0) {
        do_log(L_ERROR, "Couldn't schedule serving.\n");
        abortObject(request->object, 503, 
                    internAtom("Couldn't schedule serving"));
    }
    return 1;
}

static int
httpServeObjectStreamHandlerCommon(int kind, int status,
                                   FdEventHandlerPtr event,
                                   StreamRequestPtr srequest)
{
    HTTPConnectionPtr connection = srequest->data;
    HTTPRequestPtr request = connection->request;
    int condition_result = httpCondition(request->object, request->condition);
    int i = connection->offset / CHUNK_SIZE;

    assert(!request->chandler);

    if(status == 0 && !streamRequestDone(srequest)) {
        httpSetTimeout(connection, clientTimeout);
        return 0;
    }

    httpSetTimeout(connection, -1);

    unlockChunk(request->object, i);
    if(kind == 2)
        unlockChunk(request->object, i + 1);

    if(status) {
        if(status < 0) {
            do_log_error(status == -ECONNRESET ? D_IO : L_ERROR, 
                         -status, "Couldn't write to client");
            if(status == -EIO || status == -ESHUTDOWN)
                httpClientFinish(connection, 2);
            else
                httpClientFinish(connection, 1);
        } else {
            do_log(D_IO, "Couldn't write to client: short write.\n");
            httpClientFinish(connection, 2);
        }
        return 1;
    }

    if(srequest->operation & IO_CHUNKED) {
        assert(srequest->offset > 2);
        connection->offset += srequest->offset - 2;
    } else {
        connection->offset += srequest->offset;
    }
    request->flags &= ~REQUEST_REQUESTED;

    if(request->object->flags & OBJECT_ABORTED) {
        httpClientFinish(connection, 1);
        return 1;
    }

    if(request->method == METHOD_HEAD ||
       request->object->code == 204 ||
       request->object->code < 200 ||
       condition_result == CONDITION_NOT_MODIFIED) {
        httpClientFinish(connection, 0);
        return 1;
    }

    if(srequest->operation & IO_END)
        httpClientFinish(connection, 0);
    else {
        httpConnectionDestroyBuf(connection);
        httpServeChunk(connection);
    }
    return 1;
}

int
httpServeObjectStreamHandler(int status,
                             FdEventHandlerPtr event,
                             StreamRequestPtr srequest)
{
    return httpServeObjectStreamHandlerCommon(1, status, event, srequest);
}

int
httpServeObjectStreamHandler2(int status,
                              FdEventHandlerPtr event,
                              StreamRequestPtr srequest)
{
    return httpServeObjectStreamHandlerCommon(2, status, event, srequest);
}
