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(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(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(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;