return csd->abortRequestParsing("error:method-not-allowed");
}
- hp->request_parse_status = Http::scMethodNotAllowed;
+ /* draft-ietf-httpbis-http2-16 section 11.6 registers the method PRI as HTTP/2 specific
+ * Deny "PRI" method if used in HTTP/1.x or 0.9 versions.
+ * If seen it signals a broken client or proxy has corrupted the traffic.
+ */
+ if (hp->method() == Http::METHOD_PRI && hp->messageProtocol() < Http::ProtocolVersion(2,0)) {
+ debugs(33, DBG_IMPORTANT, "WARNING: PRI method received on " << csd->transferProtocol << " port " << csd->port->s.port());
+ debugs(33, DBG_IMPORTANT, "WARNING: for request: " << hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol());
++ hp->parseStatusCode = Http::scMethodNotAllowed;
+ return csd->abortRequestParsing("error:method-not-allowed");
+ }
+
if (hp->method() == Http::METHOD_NONE) {
debugs(33, DBG_IMPORTANT, "WARNING: Unsupported method: " << hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol());
- hp->request_parse_status = Http::scMethodNotAllowed;
+ hp->parseStatusCode = Http::scMethodNotAllowed;
return csd->abortRequestParsing("error:unsupported-request-method");
}
return;
}
- Http::StatusCode error = Http::scNone;
+ /* Attempt to parse the first line; this will define where the protocol, status, reason-phrase and header begin */
+ {
+ if (hp == NULL)
+ hp = new Http1::ResponseParser;
+
+ bool parsedOk = hp->parse(inBuf);
+
+ // sync the buffers after parsing.
+ inBuf = hp->remaining();
+
+ if (hp->needsMoreData()) {
+ if (eof) { // no more data coming
+ /* Bug 2879: Replies may terminate with \r\n then EOF instead of \r\n\r\n.
+ * We also may receive truncated responses.
+ * Ensure here that we have at minimum two \r\n when EOF is seen.
+ */
+ inBuf.append("\r\n\r\n", 4);
+ // retry the parse
+ parsedOk = hp->parse(inBuf);
+ // sync the buffers after parsing.
+ inBuf = hp->remaining();
+ } else {
+ debugs(33, 5, "Incomplete response, waiting for end of response headers");
+ ctx_exit(ctx);
+ return;
+ }
+ }
- HttpReply *newrep = new HttpReply;
- const bool parsed = newrep->parse(readBuf, eof, &error);
-
- if (!parsed && readBuf->contentSize() > 5 && strncmp(readBuf->content(), "HTTP/", 5) != 0 && strncmp(readBuf->content(), "ICY", 3) != 0) {
- MemBuf *mb;
- HttpReply *tmprep = new HttpReply;
- tmprep->setHeaders(Http::scOkay, "Gatewaying", NULL, -1, -1, -1);
- tmprep->header.putExt("X-Transformed-From", "HTTP/0.9");
- mb = tmprep->pack();
- newrep->parse(mb, eof, &error);
- delete mb;
- delete tmprep;
- } else {
- if (!parsed && error > 0) { // unrecoverable parsing error
- debugs(11, 3, "processReplyHeader: Non-HTTP-compliant header: '" << readBuf->content() << "'");
- flags.headers_parsed = true;
- // XXX: when sanityCheck is gone and Http::StatusLine is used to parse,
- // the sline should be already set the appropriate values during that parser stage
- newrep->sline.set(Http::ProtocolVersion(), error);
+ flags.headers_parsed = true;
+
+ if (!parsedOk) {
+ // unrecoverable parsing error
+ debugs(11, 3, "Non-HTTP-compliant header:\n---------\n" << inBuf << "\n----------");
+ HttpReply *newrep = new HttpReply;
- newrep->sline.set(Http::ProtocolVersion(1,1), hp->messageStatus());
++ newrep->sline.set(Http::ProtocolVersion(), hp->messageStatus());
HttpReply *vrep = setVirginReply(newrep);
entry->replaceHttpReply(vrep);
+ // XXX: close the server connection ?
ctx_exit(ctx);
return;
}
// update peer response time stats (%<pt)
const timeval &sent = request->hier.peer_http_request_sent;
- request->hier.peer_response_time =
- sent.tv_sec ? tvSubMsec(sent, current_time) : -1;
+ if (sent.tv_sec)
+ tvSub(request->hier.peer_response_time, sent, current_time);
+ else
+ request->hier.peer_response_time.tv_sec = -1;
}
- /** \par
- * Here the RFC says we should ignore whitespace between replies, but we can't as
- * doing so breaks HTTP/0.9 replies beginning with witespace, and in addition
- * the response splitting countermeasures is extremely likely to trigger on this,
- * not allowing connection reuse in the first place.
- *
- * 2012-02-10: which RFC? not 2068 or 2616,
- * tolerance there is all about whitespace between requests and header tokens.
- */
+ /* Continue to process previously read data */
+ break;
- if (len == 0) { // reached EOF?
+ case Comm::ENDFILE: // close detected by 0-byte read
eof = 1;
flags.do_next_read = false;
// Determine whether the response is a cacheable representation
int cacheableReply();
- CachePeer *_peer; /* CachePeer request made to */
- int eof; /* reached end-of-object? */
- int lastChunk; /* reached last chunk of a chunk-encoded reply */
+ CachePeer *_peer; /* CachePeer request made to */
+ int eof; /* reached end-of-object? */
+ int lastChunk; /* reached last chunk of a chunk-encoded reply */
HttpStateFlags flags;
size_t read_sz;
- int header_bytes_read; // to find end of response,
- int64_t reply_bytes_read; // without relying on StoreEntry
- int body_bytes_truncated; // positive when we read more than we wanted
- MemBuf *readBuf;
+ SBuf inBuf; ///< I/O buffer for receiving server responses
bool ignoreCacheControl;
bool surrogateNoStore;
mimeHeaderBlock_.clear();
}
+bool
+Http::One::Parser::findMimeBlock(const char *which, size_t limit)
+{
+ if (msgProtocol_.major == 1) {
+ /* NOTE: HTTP/0.9 messages do not have a mime header block.
+ * So the rest of the code will need to deal with '0'-byte headers
+ * (ie, none, so don't try parsing em)
+ */
+ int64_t mimeHeaderBytes = 0;
+ // XXX: c_str() reallocates. performance regression.
+ if ((mimeHeaderBytes = headersEnd(buf_.c_str(), buf_.length())) == 0) {
+ if (buf_.length()+firstLineSize() >= limit) {
+ debugs(33, 5, "Too large " << which);
+ parseStatusCode = Http::scHeaderTooLarge;
+ parsingStage_ = HTTP_PARSE_DONE;
+ } else
+ debugs(33, 5, "Incomplete " << which << ", waiting for end of headers");
+ return false;
+ }
+ mimeHeaderBlock_ = buf_.consume(mimeHeaderBytes);
+ debugs(74, 5, "mime header (0-" << mimeHeaderBytes << ") {" << mimeHeaderBlock_ << "}");
+
+ } else
+ debugs(33, 3, "Missing HTTP/1.x identifier");
+
+ // NP: we do not do any further stages here yet so go straight to DONE
+ parsingStage_ = HTTP_PARSE_DONE;
+
+ // Squid could handle these headers, but admin does not want to
+ if (messageHeaderSize() >= limit) {
+ debugs(33, 5, "Too large " << which);
+ parseStatusCode = Http::scHeaderTooLarge;
+ return false;
+ }
+
+ return true;
+}
+
// arbitrary maximum-length for headers which can be found by Http1Parser::getHeaderField()
- #define GET_HDR_SZ 1024
+ #define GET_HDR_SZ 1024
// BUG: returns only the first header line with given name,
// ignores multi-line headers and obs-fold headers
#include "SquidConfig.h"
Http::One::RequestParser::RequestParser() :
- Parser()
- Parser(),
- request_parse_status(Http::scNone)
++ Parser()
{
req.start = req.end = -1;
req.m_start = req.m_end = -1;
// However it does explicitly state an exact syntax which omits un-encoded CR
// and defines 400 (Bad Request) as the required action when
// handed an invalid request-line.
- request_parse_status = Http::scBadRequest;
+ parseStatusCode = Http::scBadRequest;
return -1;
}
- request_parse_status = Http::scBadRequest;
+
+ // We are expecting printable ascii characters for method/first word
+ if (first_whitespace < 0 && (!xisascii(buf_[i]) || !xisprint(buf_[i]))) {
++ parseStatusCode = Http::scBadRequest;
+ return -1;
+ }
}
if (req.end == -1) {
HttpRequest::Pointer request;
ClientHttpRequest *http = context->http;
if (context->flags.parsed_ok == 0) {
- clientStreamNode *node = context->getClientReplyContext();
debugs(33, 2, "Invalid Request");
- quitAfterError(NULL);
- // setLogUri should called before repContext->setReplyToError
- setLogUri(http, http->uri, true);
- clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
- assert(repContext);
-
// determine which error page templates to use for specific parsing errors
err_type errPage = ERR_INVALID_REQ;
- switch (parser_->request_parse_status) {
+ switch (parser_->parseStatusCode) {
case Http::scRequestHeaderFieldsTooLarge:
- // fall through to next case
+ // fall through to next case
case Http::scUriTooLong:
errPage = ERR_TOO_BIG;
break;
errPage = ERR_UNSUP_HTTPVERSION;
break;
default:
- // use default ERR_INVALID_REQ set above.
+ if (parser_->method() == METHOD_NONE || parser_->requestUri().length() == 0)
+ // no method or url parsed, probably is wrong protocol
+ errPage = ERR_PROTOCOL_UNKNOWN;
+ // else use default ERR_INVALID_REQ set above.
break;
}
- repContext->setReplyToError(errPage, parser_->parseStatusCode, parser_->method(), http->uri,
- clientConnection->remote, NULL, in.buf.c_str(), NULL);
- assert(context->http->out.offset == 0);
- context->pullData();
+ // setLogUri should called before repContext->setReplyToError
+ setLogUri(http, http->uri, true);
+ const char * requestErrorBytes = in.buf.c_str();
- if (!clientTunnelOnError(this, context, request.getRaw(), parser_->method(), errPage, parser_->request_parse_status, requestErrorBytes)) {
++ if (!clientTunnelOnError(this, context, request.getRaw(), parser_->method(), errPage, parser_->parseStatusCode, requestErrorBytes)) {
+ // HttpRequest object not build yet, there is no reason to call
+ // clientProcessRequestFinished method
+ }
+
return false;
}