]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Merge from trunk rev.13539
authorAmos Jeffries <squid3@treenet.co.nz>
Thu, 21 Aug 2014 18:25:14 +0000 (11:25 -0700)
committerAmos Jeffries <squid3@treenet.co.nz>
Thu, 21 Aug 2014 18:25:14 +0000 (11:25 -0700)
1  2 
configure.ac
src/Store.h
src/client_side.cc
src/client_side.h
src/servers/FtpServer.cc
src/servers/FtpServer.h
src/servers/HttpServer.cc

diff --cc configure.ac
Simple merge
diff --cc src/Store.h
Simple merge
index 6d91e8f5104bdf43d4b2a28fc98b5cc2dea474e3,b801ed6d06b57804b8642e343e67858c3b72442b..0805774bfb91b08033cac571c8b9f6f4a93ea454
@@@ -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);
  
 -            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? */
index 1992f2329b06cd1dfac00619d7eae36afd96b5df,45f257b961ae0527dffdf9a9d03187c562278532..0f59632effd392ee1b2088fe8e34cd281263ac8c
@@@ -416,10 -421,12 +421,12 @@@ protected
      void clientPinnedConnectionRead(const CommIoCbParams &io);
  
      /// parse input buffer prefix into a single transfer protocol request
 -    virtual ClientSocketContext *parseOneRequest(Http::ProtocolVersion &ver) = 0;
+     /// return NULL to request more header bytes (after checking any limits)
+     /// use abortRequestParsing() to handle parsing errors w/o creating request
 +    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;
index 353b84c70f483cbbbb3a409ba094c06198b87a1e,484336f3f61a42f928123dbf1041b1193d7b0a73..e66f70633ab986f3fa72481dc1b076d0122e9670
@@@ -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<Server>(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<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()
index f230d01305e940b4889226b1825a6c51abe13d0f,3be163491a9f6ef34b1d2acd2d12ad706bf04e86..7abf03b1f8c2d058dfe25fee4f74751fa3d89cce
@@@ -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);
index e1e5803df7f09e86f1cd62d7a8da96d684c7b4b5,3d9a82724fefb7926492c78e4bcd0532c1e0c60b..b771e4b13e7b088d09b6def1ee0fd6541d0bcd6f
@@@ -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