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();
* 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;
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);
- processParsedRequest(context, http_ver);
+ context->registerWithConn();
+
+ processParsedRequest(context);
parsed_req = true; // XXX: do we really need to parse everything right NOW ?
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? */
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<Server>(this),
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<clientReplyContext *>(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 <command> [ RWS <parameter> ] OWS LF
// InlineSpaceChars are isspace(3) or RFC 959 Section 3.1.1.5.2, except
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()