]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Merge from trunk. plus some polish
authorAmos Jeffries <squid3@treenet.co.nz>
Mon, 13 Sep 2010 05:39:51 +0000 (17:39 +1200)
committerAmos Jeffries <squid3@treenet.co.nz>
Mon, 13 Sep 2010 05:39:51 +0000 (17:39 +1200)
22 files changed:
1  2 
doc/release-notes/release-3.2.sgml
src/HttpRequest.cc
src/Makefile.am
src/Server.cc
src/Server.h
src/adaptation/icap/ModXact.cc
src/adaptation/icap/Xaction.cc
src/adaptation/icap/Xaction.h
src/cf.data.pre
src/client_side.cc
src/client_side.h
src/client_side_reply.cc
src/dns_internal.cc
src/eui/Eui48.cc
src/forward.cc
src/forward.h
src/ftp.cc
src/http.cc
src/http.h
src/icmp/net_db.cc
src/main.cc
src/structs.h

Simple merge
Simple merge
diff --cc src/Makefile.am
Simple merge
diff --cc src/Server.cc
index 9af6116dd1424489c57133376580d444c13c426c,8f11549bb13c28fbcf46ede20442e2c8f7b1c51e..40d45f8b8716a63851a839351f0721a3d5f02da9
@@@ -390,13 -391,14 +393,15 @@@ ServerStateData::sentRequestBody(const 
          return;
      }
  
-     if (requestBodySource->exhausted())
+     if (!requestBodySource->exhausted())
+         sendMoreRequestBody();
+     else if (receivedWholeRequestBody)
          doneSendingRequestBody();
      else
-         sendMoreRequestBody();
+         debugs(9,3, HERE << "waiting for body production end or abort");
  }
  
 +#if 0
  bool
  ServerStateData::canSend(int fd) const
  {
diff --cc src/Server.h
Simple merge
Simple merge
Simple merge
Simple merge
diff --cc src/cf.data.pre
Simple merge
index 17e08490ab921d6ed44214b4476754352397f001,e628863b8b7baa41f70c66c500a64d684a992e3d..741e8de1f8f594b25b9cb329fb83e4ae20adcf7c
@@@ -199,13 -197,10 +198,11 @@@ static void clientUpdateSocketStats(log
  
  char *skipLeadingSpace(char *aString);
  static void connNoteUseOfBuffer(ConnStateData* conn, size_t byteCount);
- static int connKeepReadingIncompleteRequest(ConnStateData * conn);
- static void connCancelIncompleteRequests(ConnStateData * conn);
  
 -static ConnStateData *connStateCreate(const Ip::Address &peer, const Ip::Address &me, int fd, http_port_list *port);
 +static ConnStateData *connStateCreate(const Comm::ConnectionPointer &details, http_port_list *port);
  
  
 +// TODO make this return the conn for use instead.
  int
  ClientSocketContext::fd() const
  {
@@@ -2359,19 -2346,11 +2348,11 @@@ ConnStateData::clientMaybeReadData(int 
  void
  ConnStateData::clientAfterReadingRequests(int do_next_read)
  {
-     /*
-      * If (1) we are reading a message body, (2) and the connection
-      * is half-closed, and (3) we didn't get the entire HTTP request
-      * yet, then close this connection.
-      */
-     if (fd_table[clientConn->fd].flags.socket_eof) {
-         if ((int64_t)in.notYetUsed < bodySizeLeft()) {
-             /* Partial request received. Abort client connection! */
-             debugs(33, 3, "clientAfterReadingRequests: FD " << clientConn->fd << " aborted, partial request");
-             clientConn->close();
-             return;
-         }
+     // Were we expecting to read more request body from half-closed connection?
+     if (mayNeedToReadMoreBody() && commIsHalfClosed(fd)) {
 -        debugs(33, 3, HERE << "truncated body: closing half-closed FD " << fd);
 -        comm_close(fd);
++        debugs(33, 3, HERE << "truncated body: closing half-closed FD " << clientConn);
++        clientConn->close();
+         return;
      }
  
      clientMaybeReadData (do_next_read);
@@@ -2680,9 -2638,12 +2638,12 @@@ clientParseRequest(ConnStateData * conn
      HttpVersion http_ver;
      HttpParser hp;
  
 -    debugs(33, 5, "clientParseRequest: FD " << conn->fd << ": attempting to parse");
 +    debugs(33, 5, HERE << conn->clientConn << ": attempting to parse");
  
-     while (conn->in.notYetUsed > 0 && conn->bodySizeLeft() == 0) {
+     // Loop while we have read bytes that are not needed for producing the body
+     // On errors, bodyPipe may become nil, but readMoreRequests will be cleared
+     while (conn->in.notYetUsed > 0 && !conn->bodyPipe &&
+             conn->flags.readMoreRequests) {
          connStripBufferWhitespace (conn);
  
          /* Don't try to parse if the buffer is empty */
@@@ -2874,61 -2831,13 +2832,13 @@@ ConnStateData::handleRequestBodyData(
  
      size_t putSize = 0;
  
- #if FUTURE_CODE_TO_SUPPORT_CHUNKED_REQUESTS
-     // The code below works, in principle, but we cannot do dechunking
-     // on-the-fly because that would mean sending chunked requests to
-     // the next hop. Squid lacks logic to determine which servers can
-     // receive chunk requests. Squid v3.0 code cannot even handle chunked
-     // responses which we may encourage by sending chunked requests.
-     // The error generation code probably needs more work.
-     if (in.bodyParser) { // chunked body
-         debugs(33,5, HERE << "handling chunked request body for FD " << clientConn->fd);
-         bool malformedChunks = false;
-         MemBuf raw; // ChunkedCodingParser only works with MemBufs
-         raw.init(in.notYetUsed, in.notYetUsed);
-         raw.append(in.buf, in.notYetUsed);
-         try { // the parser will throw on errors
-             const mb_size_t wasContentSize = raw.contentSize();
-             BodyPipeCheckout bpc(*bodyPipe);
-             const bool parsed = in.bodyParser->parse(&raw, &bpc.buf);
-             bpc.checkIn();
-             putSize = wasContentSize - raw.contentSize();
-             if (parsed) {
-                 stopProducingFor(bodyPipe, true); // this makes bodySize known
-             } else {
-                 // parser needy state must imply body pipe needy state
-                 if (in.bodyParser->needsMoreData() &&
-                         !bodyPipe->mayNeedMoreData())
-                     malformedChunks = true;
-                 // XXX: if bodyParser->needsMoreSpace, how can we guarantee it?
-             }
-         } catch (...) { // XXX: be more specific
-             malformedChunks = true;
-         }
-         if (malformedChunks) {
-             if (bodyPipe != NULL)
-                 stopProducingFor(bodyPipe, false);
-             ClientSocketContext::Pointer context = getCurrentContext();
-             if (!context->http->out.offset) {
-                 clientStreamNode *node = context->getClientReplyContext();
-                 clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
-                 assert (repContext);
-                 repContext->setReplyToError(ERR_INVALID_REQ, HTTP_BAD_REQUEST,
-                                             METHOD_NONE, NULL, &peer.sin_addr,
-                                             NULL, NULL, NULL);
-                 context->pullData();
-             }
-             flags.readMoreRequests = false;
-             return; // XXX: is that sufficient to generate an error?
+     if (in.bodyParser) { // chunked encoding
+         if (const err_type error = handleChunkedRequestBody(putSize)) {
+             abortChunkedRequestBody(error);
+             return false;
          }
-     } else // identity encoding
- #endif
-     {
-         debugs(33,5, HERE << "handling plain request body for FD " << clientConn->fd);
+     } else { // identity encoding
 -        debugs(33,5, HERE << "handling plain request body for FD " << fd);
++        debugs(33,5, HERE << "handling plain request body for " << clientConn);
          putSize = bodyPipe->putMoreData(in.buf, in.notYetUsed);
          if (!bodyPipe->mayNeedMoreData()) {
              // BodyPipe will clear us automagically when we produced everything
          connNoteUseOfBuffer(this, putSize);
  
      if (!bodyPipe) {
-         debugs(33,5, HERE << "produced entire request body for FD " << clientConn->fd);
 -        debugs(33,5, HERE << "produced entire request body for FD " << fd);
++        debugs(33,5, HERE << "produced entire request body for " << clientConn);
  
          if (closing()) {
              /* we've finished reading like good clients,
               * now do the close that initiateClose initiated.
-              *
-              * XXX: do we have to close? why not check keepalive et.
-              *
-              * XXX: To support chunked requests safely, we need to handle
-              * the case of an endless request. This if-statement does not,
-              * because mayNeedMoreData is true if request size is not known.
               */
 -            comm_close(fd);
 +            clientConn->close();
+             return false;
          }
      }
 -    debugs(33,7, HERE << "chunked from FD " << fd << ": " << in.notYetUsed);
+     return true;
+ }
+ /// parses available chunked encoded body bytes, checks size, returns errors
+ err_type
+ ConnStateData::handleChunkedRequestBody(size_t &putSize)
+ {
 -        comm_reset_close(fd);
++    debugs(33,7, HERE << "chunked from " << clientConn << ": " << in.notYetUsed);
+     try { // the parser will throw on errors
+         if (!in.notYetUsed) // nothing to do (MemBuf::init requires this check)
+             return ERR_NONE;
+         MemBuf raw; // ChunkedCodingParser only works with MemBufs
+         // add one because MemBuf will assert if it cannot 0-terminate
+         raw.init(in.notYetUsed, in.notYetUsed+1);
+         raw.append(in.buf, in.notYetUsed);
+         const mb_size_t wasContentSize = raw.contentSize();
+         BodyPipeCheckout bpc(*bodyPipe);
+         const bool parsed = in.bodyParser->parse(&raw, &bpc.buf);
+         bpc.checkIn();
+         putSize = wasContentSize - raw.contentSize();
+         // dechunk then check: the size limit applies to _dechunked_ content
+         if (clientIsRequestBodyTooLargeForPolicy(bodyPipe->producedSize()))
+             return ERR_TOO_BIG;
+         if (parsed) {
+             finishDechunkingRequest(true);
+             Must(!bodyPipe);
+             return ERR_NONE; // nil bodyPipe implies body end for the caller
+         }
+         // if chunk parser needs data, then the body pipe must need it too
+         Must(!in.bodyParser->needsMoreData() || bodyPipe->mayNeedMoreData());
+         // if parser needs more space and we can consume nothing, we will stall
+         Must(!in.bodyParser->needsMoreSpace() || bodyPipe->buf().hasContent());
+     } catch (...) { // TODO: be more specific
+         debugs(33, 3, HERE << "malformed chunks" << bodyPipe->status());
+         return ERR_INVALID_REQ;
+     }
+     debugs(33, 7, HERE << "need more chunked data" << *bodyPipe->status());
+     return ERR_NONE;
+ }
+ /// quit on errors related to chunked request body handling
+ void
+ ConnStateData::abortChunkedRequestBody(const err_type error)
+ {
+     finishDechunkingRequest(false);
+     // XXX: The code below works if we fail during initial request parsing,
+     // but if we fail when the server-side works already, the server may send
+     // us its response too, causing various assertions. How to prevent that?
+ #if WE_KNOW_HOW_TO_SEND_ERRORS
+     ClientSocketContext::Pointer context = getCurrentContext();
+     if (context != NULL && !context->http->out.offset) { // output nothing yet
+         clientStreamNode *node = context->getClientReplyContext();
+         clientReplyContext *repContext = dynamic_cast<clientReplyContext*>(node->data.getRaw());
+         assert(repContext);
+         const http_status scode = (error == ERR_TOO_BIG) ?
+                                   HTTP_REQUEST_ENTITY_TOO_LARGE : HTTP_BAD_REQUEST;
+         repContext->setReplyToError(error, scode,
+                                     repContext->http->request->method,
+                                     repContext->http->uri,
+                                     peer,
+                                     repContext->http->request,
+                                     in.buf, NULL);
+         context->pullData();
+     } else {
+         // close or otherwise we may get stuck as nobody will notice the error?
 -    comm_reset_close(fd);
++        comm_reset_close(clientConn);
+     }
+ #else
+     debugs(33, 3, HERE << "aborting chunked request without error " << error);
++    comm_reset_close(clientConn);
+ #endif
+     flags.readMoreRequests = false;
  }
  
  void
@@@ -3050,7 -3037,7 +3038,7 @@@ clientLifetimeTimeout(int fd, void *dat
      debugs(33, 1, "WARNING: Closing client " << " connection due to lifetime timeout");
      debugs(33, 1, "\t" << http->uri);
      http->al.http.timedout = true;
--    comm_close(fd);
++    comm_close(fd); // XXX: this breaks ConnStateData::clientConn.
  }
  
  ConnStateData *
index c2379ffe6c214bb8f6ff2fe3e53c118b9d30a8f9,6f932f35dec99e87ae54700fd3fc7256ac280f78..e07a40429ed73ff2e3f0e550f3be50051b23308a
@@@ -125,8 -136,10 +136,8 @@@ private
  };
  
  
 -class ConnectionDetail;
 -
  /** A connection to a socket */
- class ConnStateData : public BodyProducer/*, public RefCountable*/
+ class ConnStateData : public BodyProducer, public HttpControlMsgSink
  {
  
  public:
      void addContextToQueue(ClientSocketContext * context);
      int getConcurrentRequestCount() const;
      bool isOpen() const;
+     void checkHeaderLimits();
+     // HttpControlMsgSink API
+     virtual void sendControlMsg(HttpControlMsg msg);
  
 -    int fd;
 +    // Client TCP connection details from comm layer.
 +    Comm::ConnectionPointer clientConn;
  
-     /// chunk buffering and parsing algorithm state
-     typedef enum { chunkUnknown, chunkNone, chunkParsing, chunkReady, chunkError } DechunkingState;
      struct In {
          In();
          ~In();
index 75bad3daff803a4018e6bc3a3ab0d75c849a287a,19763b7076bb3fbd6264bac231f98455a98ed269..0df082ea79917ee65b8c8bfc1ab576bc28c67159
@@@ -679,9 -682,12 +681,11 @@@ clientReplyContext::processMiss(
          if (http->flags.internal)
              r->protocol = PROTO_INTERNAL;
  
+         r->clientConnection = http->getConn();
          /** Start forwarding to get the new object from network */
 -        FwdState::fwdStart(http->getConn() != NULL ? http->getConn()->fd : -1,
 -                           http->storeEntry(),
 -                           r);
 +        Comm::ConnectionPointer conn = http->getConn() != NULL ? http->getConn()->clientConn : NULL;
 +        FwdState::fwdStart(conn, http->storeEntry(), r);
      }
  }
  
Simple merge
Simple merge
diff --cc src/forward.cc
index a6b09ec596b218f9ed1244e6813799fd3955cd8b,6f366785f7fbc0de354ee71dbb84b3406b1cf4a5..f61d138011911c6a93a012d9221a6a645c2a492b
@@@ -496,38 -506,47 +506,36 @@@ FwdState::serverClosed(int fd
  void
  FwdState::retryOrBail()
  {
-     if (!self) { // we have aborted before the server called us back
-         debugs(17, 5, HERE << "not retrying because of earlier abort");
-         // we will be destroyed when the server clears its Pointer to us
-         return;
-     }
      if (checkRetry()) {
 -        int originserver = (servers->_peer == NULL);
 -        debugs(17, 3, "fwdServerClosed: re-forwarding (" << n_tries << " tries, " << (squid_curtime - start_t) << " secs)");
 -
 -        if (servers->next) {
 -            /* use next, or cycle if origin server isn't last */
 -            FwdServer *fs = servers;
 -            FwdServer **T, *T2 = NULL;
 -            servers = fs->next;
 -
 -            for (T = &servers; *T; T2 = *T, T = &(*T)->next);
 -            if (T2 && T2->_peer) {
 -                /* cycle */
 -                *T = fs;
 -                fs->next = NULL;
 -            } else {
 -                /* Use next. The last "direct" entry is retried multiple times */
 -                servers = fs->next;
 -                fwdServerFree(fs);
 -                originserver = 0;
 +        debugs(17, 3, HERE << "re-forwarding (" << n_tries << " tries, " << (squid_curtime - start_t) << " secs)");
 +
 +        serverDestinations.shift(); // last one failed. try another.
 +
 +        if (serverDestinations.size() > 0) {
 +            /* Ditch error page if it was created before.
 +             * A new one will be created if there's another problem */
 +            if (err) {
 +                errorStateFree(err);
 +                err = NULL;
              }
 -        }
  
 -        /* Ditch error page if it was created before.
 -         * A new one will be created if there's another problem */
 -        if (err) {
 -            errorStateFree(err);
 -            err = NULL;
 +            connectStart();
 +            return;
          }
 +        // else bail. no more serverDestinations possible to try.
  
 -        /* use eventAdd to break potential call sequence loops and to slow things down a little */
 -        eventAdd("fwdConnectStart", fwdConnectStartWrapper, this, originserver ? 0.05 : 0.005, 0);
 -
 -        return;
 +        // AYJ: cannot-forward error ??
-         ErrorState *anErr = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
-         errorAppendEntry(entry, anErr);
++// is this hack needed since we now have doneWithRetries() below?
++//        ErrorState *anErr = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
++//        errorAppendEntry(entry, anErr);
      }
  
-     if (!err && shutting_down) {
+     // TODO: should we call completed() here and move doneWithRetries there?
+     doneWithRetries();
+     if (self != NULL && !err && shutting_down) {
 -        errorCon(ERR_SHUTTING_DOWN, HTTP_SERVICE_UNAVAILABLE, request);
 +        ErrorState *anErr = errorCon(ERR_SHUTTING_DOWN, HTTP_SERVICE_UNAVAILABLE, request);
 +        errorAppendEntry(entry, anErr);
      }
  
      self = NULL;      // refcounted
diff --cc src/forward.h
Simple merge
diff --cc src/ftp.cc
Simple merge
diff --cc src/http.cc
index 83e9f6000dccef30a3fa6e53817e851d0ee9fdd7,3505ec1ce26f35d0c6125852bf62d56ee7261bad..abf291c9592d5013655f123356b38e7c2ba69f2c
@@@ -42,8 -42,8 +42,9 @@@
  
  #include "acl/FilledChecklist.h"
  #include "auth/UserRequest.h"
+ #include "base/AsyncJobCalls.h"
  #include "base/TextException.h"
 +#include "comm/Connection.h"
  #if DELAY_POOLS
  #include "DelayPools.h"
  #endif
@@@ -162,17 -165,14 +164,17 @@@ HttpStateData::~HttpStateData(
  
      HTTPMSGUNLOCK(orig_request);
  
 -    debugs(11,5, HERE << "HttpStateData " << this << " destroyed; FD " << fd);
 +    cbdataReferenceDone(_peer);
 +
-     debugs(11,5, HERE << "HttpStateData " << this << " destroyed; FD " << (serverConnection!=NULL?serverConnection->fd:-1) );
++    debugs(11,5, HERE << "HttpStateData " << this << " destroyed; " << serverConnection);
  }
  
 -int
 +const Comm::ConnectionPointer &
  HttpStateData::dataDescriptor() const
  {
 -    return fd;
 +    return serverConnection;
  }
 +
  /*
  static void
  httpStateFree(int fd, void *data)
@@@ -205,7 -205,7 +207,7 @@@ httpCachable(const HttpRequestMethod& m
  void
  HttpStateData::httpTimeout(const CommTimeoutCbParams &params)
  {
-     debugs(11, 4, "httpTimeout: FD " << serverConnection->fd << ": '" << entry->url() << "'" );
 -    debugs(11, 4, "httpTimeout: FD " << fd << ": '" << entry->url() << "'" );
++    debugs(11, 4, HERE << serverConnection << ": '" << entry->url() << "'" );
  
      if (entry->store_status == STORE_PENDING) {
          fwd->fail(errorCon(ERR_READ_TIMEOUT, HTTP_GATEWAY_TIMEOUT, fwd->request));
@@@ -956,7 -1002,7 +1004,7 @@@ HttpStateData::statusIfComplete() cons
  HttpStateData::ConnectionStatus
  HttpStateData::persistentConnStatus() const
  {
-     debugs(11, 3, "persistentConnStatus: FD " << serverConnection->fd << " eof=" << eof);
 -    debugs(11, 3, "persistentConnStatus: FD " << fd << " eof=" << eof);
++    debugs(11, 3, HERE << serverConnection << " eof=" << eof);
      const HttpReply *vrep = virginReply();
      debugs(11, 5, "persistentConnStatus: content_length=" << vrep->content_length);
  
@@@ -1030,7 -1076,7 +1078,7 @@@ HttpStateData::readReply(const CommIoCb
      int clen;
      int len = io.size;
  
-     assert(serverConnection->fd == io.fd);
 -    assert(fd == io.fd);
++//    assert(serverConnection->fd == io.fd); // XXX: false when closing. serverConnection-> will already be -1.
  
      flags.do_next_read = 0;
  
@@@ -1387,7 -1455,7 +1456,7 @@@ HttpStateData::maybeReadVirginBody(
      const int read_size = replyBodySpace(*readBuf, minRead);
  
      debugs(11,9, HERE << (flags.do_next_read ? "may" : "wont") <<
-            " read up to " << read_size << " bytes from FD " << serverConnection->fd);
 -           " read up to " << read_size << " bytes from FD " << fd);
++           " read up to " << read_size << " bytes from " << serverConnection);
  
      /*
       * why <2? Because delayAwareRead() won't actually read if
      }
  }
  
- /*
-  * This will be called when request write is complete.
-  */
+ /// called after writing the very last request byte (body, last-chunk, etc)
  void
- HttpStateData::sendComplete(const CommIoCbParams &io)
+ HttpStateData::wroteLast(const CommIoCbParams &io)
  {
-     debugs(11, 5, "httpSendComplete: FD " << serverConnection->fd << ": size " << io.size << ": errflag " << io.flag << ".");
 -    debugs(11, 5, HERE << "FD " << fd << ": size " << io.size << ": errflag " << io.flag << ".");
++    debugs(11, 5, HERE << serverConnection << ": size " << io.size << ": errflag " << io.flag << ".");
  #if URL_CHECKSUM_DEBUG
  
      entry->mem_obj->checkUrlChecksum();
@@@ -1461,22 -1534,24 +1535,22 @@@ HttpStateData::sendComplete(
  void
  HttpStateData::closeServer()
  {
-     debugs(11,5, HERE << "closing HTTP server FD " << serverConnection->fd << " this " << this);
 -    debugs(11,5, HERE << "closing HTTP server FD " << fd << " this " << this);
++    debugs(11,5, HERE << "closing HTTP server " << serverConnection << " this " << this);
  
-     if (serverConnection->isOpen()) {
 -    if (fd >= 0) {
 -        fwd->unregister(fd);
 -        comm_remove_close_handler(fd, closeHandler);
++    if (Comm::IsConnOpen(serverConnection)) {
 +        fwd->unregister(serverConnection);
 +        comm_remove_close_handler(serverConnection->fd, closeHandler);
          closeHandler = NULL;
 -        comm_close(fd);
 -        fd = -1;
 +        serverConnection->close();
      }
  }
  
  bool
  HttpStateData::doneWithServer() const
  {
-     return serverConnection == NULL || !serverConnection->isOpen();
 -    return fd < 0;
++    return !Comm::IsConnOpen(serverConnection);
  }
  
 -
  /*
   * Fixup authentication request headers for special cases
   */
@@@ -1978,10 -2061,10 +2060,10 @@@ HttpStateData::sendRequest(
  {
      MemBuf mb;
  
-     debugs(11, 5, "httpSendRequest: FD " << serverConnection->fd << ", request " << request << ", this " << this << ".");
 -    debugs(11, 5, "httpSendRequest: FD " << fd << ", request " << request << ", this " << this << ".");
++    debugs(11, 5, HERE << serverConnection << ", request " << request << ", this " << this << ".");
  
 -    if (!canSend(fd)) {
 -        debugs(11,3, HERE << "cannot send request to closing FD " << fd);
 +    if (!Comm::IsConnOpen(serverConnection)) {
-         debugs(11,3, HERE << "cannot send request to closing FD " << serverConnection->fd);
++        debugs(11,3, HERE << "cannot send request to closing " << serverConnection);
          assert(closeHandler != NULL);
          return false;
      }
      mb.init();
      request->peer_host=_peer?_peer->host:NULL;
      buildRequestPrefix(request, orig_request, entry, &mb, flags);
-     debugs(11, 6, "httpSendRequest: FD " << serverConnection->fd << ":\n" << mb.buf);
 -    debugs(11, 6, "httpSendRequest: FD " << fd << ":\n" << mb.buf);
 -    comm_write_mbuf(fd, &mb, requestSender);
++    debugs(11, 6, HERE << serverConnection << ":\n" << mb.buf);
 +    comm_write_mbuf(serverConnection->fd, &mb, requestSender);
  
      return true;
  }
@@@ -2073,42 -2190,72 +2189,72 @@@ httpStart(FwdState *fwd
       */
  }
  
- void
- HttpStateData::doneSendingRequestBody()
+ /// if broken posts are enabled for the request, try to fix and return true
+ bool
+ HttpStateData::finishingBrokenPost()
  {
-     debugs(11,5, HERE << "doneSendingRequestBody: FD " << serverConnection->fd);
  #if USE_HTTP_VIOLATIONS
-     if (Config.accessList.brokenPosts) {
-         ACLFilledChecklist ch(Config.accessList.brokenPosts, request, NULL);
-         if (!ch.fastCheck()) {
-             debugs(11, 5, "doneSendingRequestBody: didn't match brokenPosts");
-             CommIoCbParams io(NULL);
-             io.fd = serverConnection->fd;
-             io.flag = COMM_OK;
-             sendComplete(io);
-         } else {
-             debugs(11, 2, "doneSendingRequestBody: matched brokenPosts");
+     if (!Config.accessList.brokenPosts) {
+         debugs(11, 5, HERE << "No brokenPosts list");
+         return false;
+     }
  
-             if (!Comm::IsConnOpen(serverConnection)) {
-                 debugs(11,2, HERE << "cannot send CRLF to closing FD");
-                 assert(closeHandler != NULL);
-                 return;
-             }
+     ACLFilledChecklist ch(Config.accessList.brokenPosts, request, NULL);
+     if (!ch.fastCheck()) {
+         debugs(11, 5, HERE << "didn't match brokenPosts");
+         return false;
+     }
  
-             typedef CommCbMemFunT<HttpStateData, CommIoCbParams> Dialer;
-             AsyncCall::Pointer call = JobCallback(11, 5, Dialer, this, HttpStateData::sendComplete);
-             comm_write(serverConnection->fd, "\r\n", 2, call);
-         }
-         return;
 -    if (!canSend(fd)) {
 -        debugs(11,2, HERE << "ignoring broken POST for closing FD " << fd);
++    if (!Comm::IsConnOpen(serverConnection)) {
++        debugs(11,2, HERE << "ignoring broken POST for closed " << serverConnection);
+         assert(closeHandler != NULL);
+         return true; // prevent caller from proceeding as if nothing happened
      }
-     debugs(11, 5, "doneSendingRequestBody: No brokenPosts list");
+     debugs(11, 2, "finishingBrokenPost: fixing broken POST");
+     typedef CommCbMemFunT<HttpStateData, CommIoCbParams> Dialer;
+     requestSender = JobCallback(11,5,
+                                 Dialer, this, HttpStateData::wroteLast);
 -    comm_write(fd, "\r\n", 2, requestSender);
++    comm_write(serverConnection->fd, "\r\n", 2, requestSender);
+     return true;
+ #else
+     return false;
  #endif /* USE_HTTP_VIOLATIONS */
 -    comm_write(fd, "0\r\n\r\n", 5, requestSender);
+ }
+ /// if needed, write last-chunk to end the request body and return true
+ bool
+ HttpStateData::finishingChunkedRequest()
+ {
+     if (flags.sentLastChunk) {
+         debugs(11, 5, HERE << "already sent last-chunk");
+         return false;
+     }
+     Must(receivedWholeRequestBody); // or we should not be sending last-chunk
+     flags.sentLastChunk = true;
+     typedef CommCbMemFunT<HttpStateData, CommIoCbParams> Dialer;
+     requestSender = JobCallback(11,5,
+                                 Dialer, this, HttpStateData::wroteLast);
 -    debugs(11,5, HERE << "doneSendingRequestBody: FD " << fd);
++    comm_write(serverConnection->fd, "0\r\n\r\n", 5, requestSender);
+     return true;
+ }
+ void
+ HttpStateData::doneSendingRequestBody()
+ {
+     ServerStateData::doneSendingRequestBody();
++    debugs(11,5, HERE << serverConnection);
+     // do we need to write something after the last body byte?
+     const bool chunked = request->header.chunked();
+     if (chunked && finishingChunkedRequest())
+         return;
+     if (!chunked && finishingBrokenPost())
+         return;
  
-     CommIoCbParams io(NULL);
-     io.fd = serverConnection->fd;
-     io.flag = COMM_OK;
-     sendComplete(io);
+     sendComplete();
  }
  
  // more origin request body data is available
  HttpStateData::abortTransaction(const char *reason)
  {
      debugs(11,5, HERE << "aborting transaction for " << reason <<
-            "; FD " << serverConnection->fd << ", this " << this);
 -           "; FD " << fd << ", this " << this);
++           "; " << serverConnection << ", this " << this);
  
 -    if (fd >= 0) {
 -        comm_close(fd);
 +    if (serverConnection->isOpen()) {
 +        serverConnection->close();
          return;
      }
  
diff --cc src/http.h
index d64f552300de845ef1b56053904fea1320bd5afe,b53092a3ba306d997bba259ef8cd1569146730b0..97b4a393d6042541703a948711407fe104c6bd3f
@@@ -81,13 -81,11 +81,17 @@@ public
  protected:
      virtual HttpRequest *originalRequest();
  
+     void processReply();
+     void proceedAfter1xx();
+     void handle1xx(HttpReply *msg);
  private:
 +    /**
 +     * The current server connection.
 +     * Maybe open, closed, or NULL.
 +     * Use doneWithServer() to check if the server is available for use.
 +     */
 +    Comm::ConnectionPointer serverConnection;
      AsyncCall::Pointer closeHandler;
      enum ConnectionStatus {
          INCOMPLETE_MSG,
Simple merge
diff --cc src/main.cc
Simple merge
diff --cc src/structs.h
Simple merge