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;