From: Amos Jeffries Date: Thu, 21 Aug 2014 18:25:14 +0000 (-0700) Subject: Merge from trunk rev.13539 X-Git-Tag: merge-candidate-3-v1~506^2~8 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=943cdf6debefcbd851743a52088bc08aa5b7abe7;p=thirdparty%2Fsquid.git Merge from trunk rev.13539 --- 943cdf6debefcbd851743a52088bc08aa5b7abe7 diff --cc src/client_side.cc index 6d91e8f510,b801ed6d06..0805774bfb --- a/src/client_side.cc +++ b/src/client_side.cc @@@ -2042,39 -2031,23 +2039,39 @@@ prepareAcceleratedURL(ConnStateData * c if (conn->port->vhost) return; /* already in good shape */ - /* else we need to ignore the host name */ - url = strstr(url, "//"); + // skip the URI scheme + static const CharacterSet uriScheme = CharacterSet("URI-scheme","+-.") + CharacterSet::ALPHA + CharacterSet::DIGIT; + static const SBuf uriSchemeEnd("://"); + if (!tok.skipAll(uriScheme) || !tok.skip(uriSchemeEnd)) + break; -#if SHOULD_REJECT_UNKNOWN_URLS + // skip the authority segment + // RFC 3986 complex nested ABNF for "authority" boils down to this: + static const CharacterSet authority = CharacterSet("authority","-._~%:@[]!$&'()*+,;=") + + CharacterSet::HEXDIG + CharacterSet::ALPHA + CharacterSet::DIGIT; + if (!tok.skipAll(authority)) + break; - if (!url) { - hp->request_parse_status = Http::scBadRequest; - return conn->abortRequestParsing("error:invalid-request"); - } -#endif + static const SBuf slashUri("/"); + const SBuf t = tok.remaining(); + if (t.isEmpty()) + url = slashUri; + else if (t[0]=='/') // looks like path + url = t; + else if (t[0]=='?' || t[0]=='#') { // looks like query or fragment. fix '/' + url = slashUri; + url.append(t); + } // else do nothing. invalid path - if (url) - url = strchr(url + 2, '/'); + } while(false); - if (!url) - url = (char *) "/"; +#if SHOULD_REJECT_UNKNOWN_URLS + // reject URI which are not well-formed even after the processing above + if (url[0] != '/') { + hp->request_parse_status = Http::scBadRequest; - return parseHttpRequestAbort(conn, "error:invalid-request"); ++ return conn->abortRequestParsing("error:invalid-request"); } +#endif if (vport < 0) vport = http->getConn()->clientConnection->local.port(); @@@ -2173,63 -2145,116 +2170,63 @@@ prepareTransparentURL(ConnStateData * c * a ClientSocketContext structure on success or failure. */ ClientSocketContext * -parseHttpRequest(ConnStateData *csd, HttpParser *hp, HttpRequestMethod * method_p, Http::ProtocolVersion *http_ver) +parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp) { - char *req_hdr = NULL; - char *end; - size_t req_sz; - ClientHttpRequest *http; - ClientSocketContext *result; - StoreIOBuffer tempBuffer; - int r; - - /* pre-set these values to make aborting simpler */ - *method_p = Http::METHOD_NONE; - - /* NP: don't be tempted to move this down or remove again. - * It's the only DDoS protection old-String has against long URL */ - if ( hp->bufsiz <= 0) { - debugs(33, 5, "Incomplete request, waiting for end of request line"); - return NULL; - } else if ( (size_t)hp->bufsiz >= Config.maxRequestHeaderSize && headersEnd(hp->buf, Config.maxRequestHeaderSize) == 0) { - debugs(33, 5, "parseHttpRequest: Too large request"); - hp->request_parse_status = Http::scHeaderTooLarge; - return csd->abortRequestParsing("error:request-too-large"); - } - - /* Attempt to parse the first line; this'll define the method, url, version and header begin */ - r = HttpParserParseReqLine(hp); + /* Attempt to parse the first line; this will define where the method, url, version and header begin */ + { + const bool parsedOk = hp->parse(csd->in.buf); - if (r == 0) { - debugs(33, 5, "Incomplete request, waiting for end of request line"); - return NULL; - } - - if (r == -1) { - return csd->abortRequestParsing("error:invalid-request"); - } + // sync the buffers after parsing. + csd->in.buf = hp->remaining(); - /* Request line is valid here .. */ - *http_ver = Http::ProtocolVersion(hp->req.v_maj, hp->req.v_min); - - /* This call scans the entire request, not just the headers */ - if (hp->req.v_maj > 0) { - if ((req_sz = headersEnd(hp->buf, hp->bufsiz)) == 0) { - debugs(33, 5, "Incomplete request, waiting for end of headers"); + if (hp->needsMoreData()) { + debugs(33, 5, "Incomplete request, waiting for end of request line"); return NULL; } - } else { - debugs(33, 3, "parseHttpRequest: Missing HTTP identifier"); - req_sz = HttpParserReqSz(hp); - } - - /* We know the whole request is in hp->buf now */ - - assert(req_sz <= (size_t) hp->bufsiz); - - /* Will the following be true with HTTP/0.9 requests? probably not .. */ - /* So the rest of the code will need to deal with '0'-byte headers (ie, none, so don't try parsing em) */ - assert(req_sz > 0); - - hp->hdr_end = req_sz - 1; - hp->hdr_start = hp->req.end + 1; + if (!parsedOk) { + if (hp->request_parse_status == Http::scHeaderTooLarge) - return parseHttpRequestAbort(csd, "error:request-too-large"); ++ return csd->abortRequestParsing("error:request-too-large"); - return parseHttpRequestAbort(csd, "error:invalid-request"); - /* Enforce max_request_size */ - if (req_sz >= Config.maxRequestHeaderSize) { - debugs(33, 5, "parseHttpRequest: Too large request"); - hp->request_parse_status = Http::scHeaderTooLarge; - return csd->abortRequestParsing("error:request-too-large"); ++ return csd->abortRequestParsing("error:invalid-request"); + } } - /* Set method_p */ - *method_p = HttpRequestMethod(&hp->buf[hp->req.m_start], &hp->buf[hp->req.m_end]+1); + /* We know the whole request is in parser now */ + debugs(11, 2, "HTTP Client " << csd->clientConnection); + debugs(11, 2, "HTTP Client REQUEST:\n---------\n" << + hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol() << "\n" << + hp->mimeHeader() << + "\n----------"); /* deny CONNECT via accelerated ports */ - if (*method_p == Http::METHOD_CONNECT && csd->port != NULL && csd->port->flags.accelSurrogate) { + if (hp->method() == Http::METHOD_CONNECT && csd->port != NULL && csd->port->flags.accelSurrogate) { debugs(33, DBG_IMPORTANT, "WARNING: CONNECT method received on " << csd->port->transport.protocol << " Accelerator port " << csd->port->s.port()); - /* XXX need a way to say "this many character length string" */ - debugs(33, DBG_IMPORTANT, "WARNING: for request: " << hp->buf); + debugs(33, DBG_IMPORTANT, "WARNING: for request: " << hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol()); hp->request_parse_status = Http::scMethodNotAllowed; - return parseHttpRequestAbort(csd, "error:method-not-allowed"); + return csd->abortRequestParsing("error:method-not-allowed"); } - if (*method_p == Http::METHOD_NONE) { - /* XXX need a way to say "this many character length string" */ - debugs(33, DBG_IMPORTANT, "clientParseRequestMethod: Unsupported method in request '" << hp->buf << "'"); + 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; - return parseHttpRequestAbort(csd, "error:unsupported-request-method"); + return csd->abortRequestParsing("error:unsupported-request-method"); } - /* - * Process headers after request line - * TODO: Use httpRequestParse here. - */ - /* XXX this code should be modified to take a const char * later! */ - req_hdr = (char *) hp->buf + hp->req.end + 1; - - debugs(33, 3, "parseHttpRequest: req_hdr = {" << req_hdr << "}"); - - end = (char *) hp->buf + hp->hdr_end; - - debugs(33, 3, "parseHttpRequest: end = {" << end << "}"); - - debugs(33, 3, "parseHttpRequest: prefix_sz = " << - (int) HttpParserRequestLen(hp) << ", req_line_sz = " << - HttpParserReqSz(hp)); + // Process headers after request line + debugs(33, 3, "complete request received. " << + "prefix_sz = " << hp->messageHeaderSize() << + ", request-line-size=" << hp->firstLineSize() << + ", mime-header-size=" << hp->headerBlockSize() << + ", mime header block:\n" << hp->mimeHeader() << "\n----------"); /* Ok, all headers are received */ - http = new ClientHttpRequest(csd); + ClientHttpRequest *http = new ClientHttpRequest(csd); - http->req_sz = HttpParserRequestLen(hp); - result = new ClientSocketContext(csd->clientConnection, http); + http->req_sz = hp->messageHeaderSize(); + ClientSocketContext *result = new ClientSocketContext(csd->clientConnection, http); + + StoreIOBuffer tempBuffer; tempBuffer.data = result->reqbuf; tempBuffer.length = HTTP_REQBUF_SZ; @@@ -2814,16 -2895,16 +2811,16 @@@ ConnStateData::clientParseRequests( if (concurrentRequestQueueFilled()) break; - ClientSocketContext *context = parseOneRequest(); - Http::ProtocolVersion http_ver; - if (ClientSocketContext *context = parseOneRequest(http_ver)) { ++ if (ClientSocketContext *context = parseOneRequest()) { + debugs(33, 5, clientConnection << ": done parsing a request"); + - /* status -1 or 1 */ - if (context) { - debugs(33, 5, HERE << clientConnection << ": parsed a request"); AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout", CommTimeoutCbPtrFun(clientLifetimeTimeout, context->http)); commSetConnTimeout(clientConnection, Config.Timeout.lifetime, timeoutCall); + context->registerWithConn(); + - processParsedRequest(context, http_ver); + processParsedRequest(context); parsed_req = true; // XXX: do we really need to parse everything right NOW ? @@@ -2831,9 -2912,12 +2828,14 @@@ debugs(33, 3, HERE << "Not parsing new requests, as this request may need the connection"); break; } + } else { + debugs(33, 5, clientConnection << ": not enough request data: " << + in.buf.length() << " < " << Config.maxRequestHeaderSize); + Must(in.buf.length() < Config.maxRequestHeaderSize); + break; } + else // incomplete parse, wait for more data + break; } /* XXX where to 'finish' the parsing pass? */ diff --cc src/client_side.h index 1992f2329b,45f257b961..0f59632eff --- a/src/client_side.h +++ b/src/client_side.h @@@ -416,10 -421,12 +421,12 @@@ protected void clientPinnedConnectionRead(const CommIoCbParams &io); /// parse input buffer prefix into a single transfer protocol request + /// return NULL to request more header bytes (after checking any limits) + /// use abortRequestParsing() to handle parsing errors w/o creating request - virtual ClientSocketContext *parseOneRequest(Http::ProtocolVersion &ver) = 0; + virtual ClientSocketContext *parseOneRequest() = 0; /// start processing a freshly parsed request - virtual void processParsedRequest(ClientSocketContext *context, const Http::ProtocolVersion &ver) = 0; + virtual void processParsedRequest(ClientSocketContext *context) = 0; /// returning N allows a pipeline of 1+N requests (see pipeline_prefetch) virtual int pipelinePrefetchMax() const; diff --cc src/servers/FtpServer.cc index 353b84c70f,484336f3f6..e66f70633a --- a/src/servers/FtpServer.cc +++ b/src/servers/FtpServer.cc @@@ -136,21 -128,23 +129,22 @@@ Ftp::Server::doProcessRequest( if (http->storeEntry() != NULL) { debugs(33, 4, "got an immediate response"); - assert(http->storeEntry() != NULL); clientSetKeepaliveFlag(http); context->pullData(); - } else if (fwd) { + } else if (mayForward) { debugs(33, 4, "forwarding request to server side"); assert(http->storeEntry() == NULL); - clientProcessRequest(this, NULL /*parser*/, context.getRaw(), - request->method, request->http_ver); + clientProcessRequest(this, Http1::RequestParserPointer(), context.getRaw()); } else { debugs(33, 4, "will resume processing later"); } } void -Ftp::Server::processParsedRequest(ClientSocketContext *context, const Http::ProtocolVersion &) +Ftp::Server::processParsedRequest(ClientSocketContext *context) { + Must(getConcurrentRequestCount() == 1); + // Process FTP request asynchronously to make sure FTP // data connection accept callback is fired first. CallJobHere(33, 4, CbcPointer(this), @@@ -545,11 -539,81 +539,81 @@@ Ftp::CommandHasPathParameter(const SBu return PathedCommands.find(cmd) != PathedCommands.end(); } + /// creates a context filled with an error message for a given early error + ClientSocketContext * + Ftp::Server::earlyError(const EarlyErrorKind eek) + { + /* Default values, to be updated by the switch statement below */ + int scode = 421; + const char *reason = "Internal error"; + const char *errUri = "error:ftp-internal-early-error"; + + switch (eek) { + case eekHugeRequest: + scode = 421; + reason = "Huge request"; + errUri = "error:ftp-huge-request"; + break; + + case eekMissingLogin: + scode = 530; + reason = "Must login first"; + errUri = "error:ftp-must-login-first"; + break; + + case eekMissingUsername: + scode = 501; + reason = "Missing username"; + errUri = "error:ftp-missing-username"; + break; + + case eekMissingHost: + scode = 501; + reason = "Missing host"; + errUri = "error:ftp-missing-host"; + break; + + case eekUnsupportedCommand: + scode = 502; + reason = "Unknown or unsupported command"; + errUri = "error:ftp-unsupported-command"; + break; + + case eekInvalidUri: + scode = 501; + reason = "Invalid URI"; + errUri = "error:ftp-invalid-uri"; + break; + + case eekMalformedCommand: + scode = 421; + reason = "Malformed command"; + errUri = "error:ftp-malformed-command"; + break; + + // no default so that a compiler can check that we have covered all cases + } + + ClientSocketContext *context = abortRequestParsing(errUri); + clientStreamNode *node = context->getClientReplyContext(); + Must(node); + clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); + + // We cannot relay FTP scode/reason via HTTP-specific ErrorState. + // TODO: When/if ErrorState can handle native FTP errors, use it instead. + HttpReply *reply = Ftp::HttpReplyWrapper(scode, reason, Http::scBadRequest, -1); + repContext->setReplyToReply(reply); + return context; + } + /// Parses a single FTP request on the control connection. - /// Returns NULL on errors and incomplete requests. + /// Returns a new ClientSocketContext on valid requests and all errors. + /// Returns NULL on incomplete requests that may still succeed given more data. ClientSocketContext * -Ftp::Server::parseOneRequest(Http::ProtocolVersion &ver) +Ftp::Server::parseOneRequest() { + flags.readMore = false; // common for all but one case below + // OWS [ RWS ] OWS LF // InlineSpaceChars are isspace(3) or RFC 959 Section 3.1.1.5.2, except @@@ -632,14 -706,14 +706,13 @@@ HttpRequest *const request = HttpRequest::CreateFromUrlAndMethod(newUri, method); if (!request) { debugs(33, 5, "Invalid FTP URL: " << uri); - writeEarlyReply(501, "Invalid host"); uri.clear(); safe_free(newUri); - return NULL; + return earlyError(eekInvalidUri); } - ver = Http::ProtocolVersion(Ftp::ProtocolVersion().major, Ftp::ProtocolVersion().minor); request->flags.ftpNative = true; - request->http_ver = ver; + request->http_ver = Http::ProtocolVersion(Ftp::ProtocolVersion().major, Ftp::ProtocolVersion().minor); // Our fake Request-URIs are not distinctive enough for caching to work request->flags.cachable = false; // XXX: reset later by maybeCacheable() diff --cc src/servers/FtpServer.h index f230d01305,3be163491a..7abf03b1f8 --- a/src/servers/FtpServer.h +++ b/src/servers/FtpServer.h @@@ -57,9 -57,20 +57,20 @@@ public protected: friend void StartListening(); + // errors detected before it is possible to create an HTTP request wrapper + typedef enum { + eekHugeRequest, + eekMissingLogin, + eekMissingUsername, + eekMissingHost, + eekUnsupportedCommand, + eekInvalidUri, + eekMalformedCommand + } EarlyErrorKind; + /* ConnStateData API */ - virtual ClientSocketContext *parseOneRequest(Http::ProtocolVersion &ver); - virtual void processParsedRequest(ClientSocketContext *context, const Http::ProtocolVersion &ver); + virtual ClientSocketContext *parseOneRequest(); + virtual void processParsedRequest(ClientSocketContext *context); virtual void notePeerConnection(Comm::ConnectionPointer conn); virtual void clientPinnedConnectionClosed(const CommCloseCbParams &io); virtual void handleReply(HttpReply *header, StoreIOBuffer receivedData); diff --cc src/servers/HttpServer.cc index e1e5803df7,3d9a82724f..b771e4b13e --- a/src/servers/HttpServer.cc +++ b/src/servers/HttpServer.cc @@@ -122,12 -114,9 +122,9 @@@ Http::Server::parseOneRequest( } void -Http::Server::processParsedRequest(ClientSocketContext *context, const Http::ProtocolVersion &ver) +Http::Server::processParsedRequest(ClientSocketContext *context) { - /* We have an initial client stream in place should it be needed */ - /* setup our private context */ - context->registerWithConn(); - clientProcessRequest(this, &parser_, context, method_, ver); + clientProcessRequest(this, parser_, context); } void