Compliance: respond to OPTIONS requests with zero Max-Forwards value.

RFC 2616 section 9.2 says that a proxy MUST NOT forward requests with
a zero Max-Forwards value. RFC 2616 does not define proper OPTIONS
responses, so we consider successful responses optional and reply with
501 Not Implemented.

While TRACE and OPTIONS are similar with regard to Max-Forwards, we handle them in different places because OPTIONS do not need to echo the request via Store.

Co-Advisor test case: test_case/rfc2616/maxForwardsZero-OPTIONS-absolute

Compliance: do not forward OPTIONS requests with zero Max-Forwards value.

RFC 2616 section 9.2 says that a proxy MUST NOT forward requests with
a zero Max-Forwards value. RFC 2616 does not define proper OPTIONS
responses, so we consider successful responses optional and reply with
501 Not Implemented.

While TRACE and OPTIONS are similar with regard to Max-Forwards, we handle
them in different places because OPTIONS do not need to echo the request
via Store.

Co-Advisor test case: test_case/rfc2616/maxForwardsZero-OPTIONS-absolute

=== modified file 'src/client_side.cc'
--- src/client_side.cc	2010-08-07 14:22:54 +0000
+++ src/client_side.cc	2010-08-19 15:31:38 +0000
@@ -2356,40 +2356,41 @@ ConnStateData::clientAfterReadingRequest
     if (fd_table[fd].flags.socket_eof) {
         if ((int64_t)in.notYetUsed < bodySizeLeft()) {
             /* Partial request received. Abort client connection! */
             debugs(33, 3, "clientAfterReadingRequests: FD " << fd << " aborted, partial request");
             comm_close(fd);
             return;
         }
     }
 
     clientMaybeReadData (do_next_read);
 }
 
 static void
 clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *context, const HttpRequestMethod& method, HttpVersion http_ver)
 {
     ClientHttpRequest *http = context->http;
     HttpRequest *request = NULL;
     bool notedUseOfBuffer = false;
     bool tePresent = false;
     bool deChunked = false;
+    bool mustReplyToOptions = false;
     bool unsupportedTe = false;
 
     /* We have an initial client stream in place should it be needed */
     /* setup our private context */
     context->registerWithConn();
 
     if (context->flags.parsed_ok == 0) {
         clientStreamNode *node = context->getClientReplyContext();
         debugs(33, 1, "clientProcessRequest: Invalid Request");
         clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
         assert (repContext);
         switch (hp->request_parse_status) {
         case HTTP_HEADER_TOO_LARGE:
             repContext->setReplyToError(ERR_TOO_BIG, HTTP_HEADER_TOO_LARGE, method, http->uri, conn->peer, NULL, conn->in.buf, NULL);
             break;
         case HTTP_METHOD_NOT_ALLOWED:
             repContext->setReplyToError(ERR_UNSUP_REQ, HTTP_METHOD_NOT_ALLOWED, method, http->uri, conn->peer, NULL, conn->in.buf, NULL);
             break;
         default:
             repContext->setReplyToError(ERR_INVALID_REQ, HTTP_BAD_REQUEST, method, http->uri, conn->peer, NULL, conn->in.buf, NULL);
@@ -2481,42 +2482,46 @@ clientProcessRequest(ConnStateData *conn
 #if USE_SQUID_EUI
     request->client_eui48 = conn->peer_eui48;
     request->client_eui64 = conn->peer_eui64;
 #endif
 #if FOLLOW_X_FORWARDED_FOR
     request->indirect_client_addr = conn->peer;
 #endif /* FOLLOW_X_FORWARDED_FOR */
     request->my_addr = conn->me;
     request->http_ver = http_ver;
 
     tePresent = request->header.has(HDR_TRANSFER_ENCODING);
     deChunked = conn->in.dechunkingState == ConnStateData::chunkReady;
     if (deChunked) {
         assert(tePresent);
         request->setContentLength(conn->in.dechunked.contentSize());
         request->header.delById(HDR_TRANSFER_ENCODING);
         conn->finishDechunkingRequest(hp);
     } else
         conn->cleanDechunkingRequest();
 
+    if (method == METHOD_TRACE || method == METHOD_OPTIONS)
+        request->max_forwards = request->header.getInt64(HDR_MAX_FORWARDS);
+
+    mustReplyToOptions = (method == METHOD_OPTIONS) && (request->max_forwards == 0);
     unsupportedTe = tePresent && !deChunked;
-    if (!urlCheckRequest(request) || unsupportedTe) {
+    if (!urlCheckRequest(request) || mustReplyToOptions || unsupportedTe) {
         clientStreamNode *node = context->getClientReplyContext();
         clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
         assert (repContext);
         repContext->setReplyToError(ERR_UNSUP_REQ,
                                     HTTP_NOT_IMPLEMENTED, request->method, NULL,
                                     conn->peer, request, NULL, NULL);
         assert(context->http->out.offset == 0);
         context->pullData();
         conn->flags.readMoreRequests = false;
         goto finish;
     }
 
 
     if (!clientIsContentLengthValid(request)) {
         clientStreamNode *node = context->getClientReplyContext();
         clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
         assert (repContext);
         repContext->setReplyToError(ERR_INVALID_REQ,
                                     HTTP_LENGTH_REQUIRED, request->method, NULL,
                                     conn->peer, request, NULL, NULL);

=== modified file 'src/client_side_reply.cc'
--- src/client_side_reply.cc	2010-08-14 02:58:39 +0000
+++ src/client_side_reply.cc	2010-08-19 15:32:18 +0000
@@ -1598,41 +1598,41 @@ clientGetMoreData(clientStreamNode * aNo
     if (!context->ourNode)
         context->ourNode = aNode;
 
     /* no cbdatareference, this is only used once, and safely */
     if (context->flags.storelogiccomplete) {
         StoreIOBuffer tempBuffer;
         tempBuffer.offset = next->readBuffer.offset + context->headers_sz;
         tempBuffer.length = next->readBuffer.length;
         tempBuffer.data = next->readBuffer.data;
 
         storeClientCopy(context->sc, http->storeEntry(),
                         tempBuffer, clientReplyContext::SendMoreData, context);
         return;
     }
 
     if (context->http->request->method == METHOD_PURGE) {
         context->purgeRequest();
         return;
     }
 
-    /* TODO: handle OPTIONS request on max_forwards == 0 as well */
+    // OPTIONS with Max-Forwards:0 handled in clientProcessRequest()
 
     if (context->http->request->method == METHOD_TRACE) {
         if (context->http->request->max_forwards == 0) {
             context->traceReply(aNode);
             return;
         }
 
         /* continue forwarding, not finished yet. */
         http->logType = LOG_TCP_MISS;
 
         context->doGetMoreData();
     } else
         context->identifyStoreObject();
 }
 
 void
 clientReplyContext::doGetMoreData()
 {
     /* We still have to do store logic processing - vary, cache hit etc */
     if (http->storeEntry() != NULL) {

=== modified file 'src/client_side_request.cc'
--- src/client_side_request.cc	2010-07-23 10:49:32 +0000
+++ src/client_side_request.cc	2010-08-19 10:37:49 +0000
@@ -955,43 +955,40 @@ clientInterpretRequestHeaders(ClientHttp
 #if USE_USERAGENT_LOG
     if ((str = req_hdr->getStr(HDR_USER_AGENT)))
         logUserAgent(fqdnFromAddr(http->getConn()->log_addr), str);
 
 #endif
 #if USE_REFERER_LOG
 
     if ((str = req_hdr->getStr(HDR_REFERER)))
         logReferer(fqdnFromAddr(http->getConn()->log_addr), str, http->log_uri);
 
 #endif
 #if USE_FORW_VIA_DB
 
     if (req_hdr->has(HDR_X_FORWARDED_FOR)) {
         String s = req_hdr->getList(HDR_X_FORWARDED_FOR);
         fvdbCountForw(s.termedBuf());
         s.clean();
     }
 
 #endif
-    if (request->method == METHOD_TRACE || request->method == METHOD_OPTIONS) {
-        request->max_forwards = req_hdr->getInt64(HDR_MAX_FORWARDS);
-    }
 
     request->flags.cachable = http->request->cacheable();
 
     if (clientHierarchical(http))
         request->flags.hierarchical = 1;
 
     debugs(85, 5, "clientInterpretRequestHeaders: REQ_NOCACHE = " <<
            (request->flags.nocache ? "SET" : "NOT SET"));
     debugs(85, 5, "clientInterpretRequestHeaders: REQ_CACHABLE = " <<
            (request->flags.cachable ? "SET" : "NOT SET"));
     debugs(85, 5, "clientInterpretRequestHeaders: REQ_HIERARCHICAL = " <<
            (request->flags.hierarchical ? "SET" : "NOT SET"));
 
 }
 
 void
 clientRedirectDoneWrapper(void *data, char *result)
 {
     ClientRequestContext *calloutContext = (ClientRequestContext *)data;
 

Reply via email to