Example #1
0
 /*  75:    */
 /*  76:    */ public int compareToVersion(ProtocolVersion that) /*  77:    */ {
   /*  78:209 */ if (that == null) {
     /*  79:210 */ throw new IllegalArgumentException("Protocol version must not be null.");
     /*  80:    */ }
   /*  81:213 */ if (!this.protocol.equals(that.protocol)) {
     /*  82:214 */ throw new IllegalArgumentException(
         "Versions for different protocols cannot be compared. " + this + " " + that);
     /*  83:    */ }
   /*  84:219 */ int delta = getMajor() - that.getMajor();
   /*  85:220 */ if (delta == 0) {
     /*  86:221 */ delta = getMinor() - that.getMinor();
     /*  87:    */ }
   /*  88:223 */ return delta;
   /*  89:    */ }
  /**
   * Compares this protocol version with another one. Only protocol versions with the same protocol
   * name can be compared. This method does <i>not</i> define a total ordering, as it would be
   * required for {@link java.lang.Comparable}.
   *
   * @param that the protocl version to compare with
   * @return a negative integer, zero, or a positive integer as this version is less than, equal to,
   *     or greater than the argument version.
   * @throws IllegalArgumentException if the argument has a different protocol name than this
   *     object, or if the argument is <code>null</code>
   */
  public int compareToVersion(ProtocolVersion that) {
    if (that == null) {
      throw new IllegalArgumentException("Protocol version must not be null.");
    }
    if (!this.protocol.equals(that.protocol)) {
      throw new IllegalArgumentException(
          "Versions for different protocols cannot be compared. " + this + " " + that);
    }

    int delta = getMajor() - that.getMajor();
    if (delta == 0) {
      delta = getMinor() - that.getMinor();
    }
    return delta;
  }
  /**
   * Opens the connection to a http server or proxy.
   *
   * @return the opened low level connection
   * @throws IOException if the connection fails for any reason.
   */
  @Override
  AndroidHttpClientConnection openConnection(Request req) throws IOException {
    SSLSocket sslSock = null;

    if (mProxyHost != null) {
      // If we have a proxy set, we first send a CONNECT request
      // to the proxy; if the proxy returns 200 OK, we negotiate
      // a secure connection to the target server via the proxy.
      // If the request fails, we drop it, but provide the event
      // handler with the response status and headers. The event
      // handler is then responsible for cancelling the load or
      // issueing a new request.
      AndroidHttpClientConnection proxyConnection = null;
      Socket proxySock = null;
      try {
        proxySock = new Socket(mProxyHost.getHostName(), mProxyHost.getPort());

        proxySock.setSoTimeout(60 * 1000);

        proxyConnection = new AndroidHttpClientConnection();
        HttpParams params = new BasicHttpParams();
        HttpConnectionParams.setSocketBufferSize(params, 8192);

        proxyConnection.bind(proxySock, params);
      } catch (IOException e) {
        if (proxyConnection != null) {
          proxyConnection.close();
        }

        String errorMessage = e.getMessage();
        if (errorMessage == null) {
          errorMessage = "failed to establish a connection to the proxy";
        }

        throw new IOException(errorMessage);
      }

      StatusLine statusLine = null;
      int statusCode = 0;
      Headers headers = new Headers();
      try {
        BasicHttpRequest proxyReq = new BasicHttpRequest("CONNECT", mHost.toHostString());

        // add all 'proxy' headers from the original request, we also need
        // to add 'host' header unless we want proxy to answer us with a
        // 400 Bad Request
        for (Header h : req.mHttpRequest.getAllHeaders()) {
          String headerName = h.getName().toLowerCase(Locale.ROOT);
          if (headerName.startsWith("proxy")
              || headerName.equals("keep-alive")
              || headerName.equals("host")) {
            proxyReq.addHeader(h);
          }
        }

        proxyConnection.sendRequestHeader(proxyReq);
        proxyConnection.flush();

        // it is possible to receive informational status
        // codes prior to receiving actual headers;
        // all those status codes are smaller than OK 200
        // a loop is a standard way of dealing with them
        do {
          statusLine = proxyConnection.parseResponseHeader(headers);
          statusCode = statusLine.getStatusCode();
        } while (statusCode < HttpStatus.SC_OK);
      } catch (ParseException e) {
        String errorMessage = e.getMessage();
        if (errorMessage == null) {
          errorMessage = "failed to send a CONNECT request";
        }

        throw new IOException(errorMessage);
      } catch (HttpException e) {
        String errorMessage = e.getMessage();
        if (errorMessage == null) {
          errorMessage = "failed to send a CONNECT request";
        }

        throw new IOException(errorMessage);
      } catch (IOException e) {
        String errorMessage = e.getMessage();
        if (errorMessage == null) {
          errorMessage = "failed to send a CONNECT request";
        }

        throw new IOException(errorMessage);
      }

      if (statusCode == HttpStatus.SC_OK) {
        try {
          sslSock =
              (SSLSocket)
                  getSocketFactory()
                      .createSocket(proxySock, mHost.getHostName(), mHost.getPort(), true);
        } catch (IOException e) {
          if (sslSock != null) {
            sslSock.close();
          }

          String errorMessage = e.getMessage();
          if (errorMessage == null) {
            errorMessage = "failed to create an SSL socket";
          }
          throw new IOException(errorMessage);
        }
      } else {
        // if the code is not OK, inform the event handler
        ProtocolVersion version = statusLine.getProtocolVersion();

        req.mEventHandler.status(
            version.getMajor(), version.getMinor(), statusCode, statusLine.getReasonPhrase());
        req.mEventHandler.headers(headers);
        req.mEventHandler.endData();

        proxyConnection.close();

        // here, we return null to indicate that the original
        // request needs to be dropped
        return null;
      }
    } else {
      // if we do not have a proxy, we simply connect to the host
      try {
        sslSock = (SSLSocket) getSocketFactory().createSocket(mHost.getHostName(), mHost.getPort());
        sslSock.setSoTimeout(SOCKET_TIMEOUT);
      } catch (IOException e) {
        if (sslSock != null) {
          sslSock.close();
        }

        String errorMessage = e.getMessage();
        if (errorMessage == null) {
          errorMessage = "failed to create an SSL socket";
        }

        throw new IOException(errorMessage);
      }
    }

    // do handshake and validate server certificates
    SslError error =
        CertificateChainValidator.getInstance()
            .doHandshakeAndValidateServerCertificates(this, sslSock, mHost.getHostName());

    // Inform the user if there is a problem
    if (error != null) {
      // handleSslErrorRequest may immediately unsuspend if it wants to
      // allow the certificate anyway.
      // So we mark the connection as suspended, call handleSslErrorRequest
      // then check if we're still suspended and only wait if we actually
      // need to.
      synchronized (mSuspendLock) {
        mSuspended = true;
      }
      // don't hold the lock while calling out to the event handler
      boolean canHandle = req.getEventHandler().handleSslErrorRequest(error);
      if (!canHandle) {
        throw new IOException("failed to handle " + error);
      }
      synchronized (mSuspendLock) {
        if (mSuspended) {
          try {
            // Put a limit on how long we are waiting; if the timeout
            // expires (which should never happen unless you choose
            // to ignore the SSL error dialog for a very long time),
            // we wake up the thread and abort the request. This is
            // to prevent us from stalling the network if things go
            // very bad.
            mSuspendLock.wait(10 * 60 * 1000);
            if (mSuspended) {
              // mSuspended is true if we have not had a chance to
              // restart the connection yet (ie, the wait timeout
              // has expired)
              mSuspended = false;
              mAborted = true;
              if (HttpLog.LOGV) {
                HttpLog.v(
                    "HttpsConnection.openConnection():"
                        + " SSL timeout expired and request was cancelled!!!");
              }
            }
          } catch (InterruptedException e) {
            // ignore
          }
        }
        if (mAborted) {
          // The user decided not to use this unverified connection
          // so close it immediately.
          sslSock.close();
          throw new SSLConnectionClosedByUserException("connection closed by the user");
        }
      }
    }

    // All went well, we have an open, verified connection.
    AndroidHttpClientConnection conn = new AndroidHttpClientConnection();
    BasicHttpParams params = new BasicHttpParams();
    params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE, 8192);
    conn.bind(sslSock, params);

    return conn;
  }
    /**
     * Implements a patch out in 4.1.x and 4.2 that isn't available in 4.0.x which fixes a bug where
     * connections aren't reused when the response is gzipped. See
     * https://issues.apache.org/jira/browse/HTTPCORE-257 for info about the issue, and
     * http://svn.apache.org/viewvc?view=revision&revision=1124215 for the patch which is copied
     * here.
     */
    @Override
    public boolean keepAlive(final HttpResponse response, final HttpContext context) {
      if (response == null) {
        throw new IllegalArgumentException("HTTP response may not be null.");
      }
      if (context == null) {
        throw new IllegalArgumentException("HTTP context may not be null.");
      }

      // Check for a self-terminating entity. If the end of the entity
      // will
      // be indicated by closing the connection, there is no keep-alive.
      ProtocolVersion ver = response.getStatusLine().getProtocolVersion();
      Header teh = response.getFirstHeader(HTTP.TRANSFER_ENCODING);
      if (teh != null) {
        if (!HTTP.CHUNK_CODING.equalsIgnoreCase(teh.getValue())) {
          return false;
        }
      } else {
        Header[] clhs = response.getHeaders(HTTP.CONTENT_LEN);
        // Do not reuse if not properly content-length delimited
        if (clhs == null || clhs.length != 1) {
          return false;
        }
        Header clh = clhs[0];
        try {
          int contentLen = Integer.parseInt(clh.getValue());
          if (contentLen < 0) {
            return false;
          }
        } catch (NumberFormatException ex) {
          return false;
        }
      }

      // Check for the "Connection" header. If that is absent, check for
      // the "Proxy-Connection" header. The latter is an unspecified and
      // broken but unfortunately common extension of HTTP.
      HeaderIterator hit = response.headerIterator(HTTP.CONN_DIRECTIVE);
      if (!hit.hasNext()) hit = response.headerIterator("Proxy-Connection");

      // Experimental usage of the "Connection" header in HTTP/1.0 is
      // documented in RFC 2068, section 19.7.1. A token "keep-alive" is
      // used to indicate that the connection should be persistent.
      // Note that the final specification of HTTP/1.1 in RFC 2616 does
      // not
      // include this information. Neither is the "Connection" header
      // mentioned in RFC 1945, which informally describes HTTP/1.0.
      //
      // RFC 2616 specifies "close" as the only connection token with a
      // specific meaning: it disables persistent connections.
      //
      // The "Proxy-Connection" header is not formally specified anywhere,
      // but is commonly used to carry one token, "close" or "keep-alive".
      // The "Connection" header, on the other hand, is defined as a
      // sequence of tokens, where each token is a header name, and the
      // token "close" has the above-mentioned additional meaning.
      //
      // To get through this mess, we treat the "Proxy-Connection" header
      // in exactly the same way as the "Connection" header, but only if
      // the latter is missing. We scan the sequence of tokens for both
      // "close" and "keep-alive". As "close" is specified by RFC 2068,
      // it takes precedence and indicates a non-persistent connection.
      // If there is no "close" but a "keep-alive", we take the hint.

      if (hit.hasNext()) {
        try {
          TokenIterator ti = createTokenIterator(hit);
          boolean keepalive = false;
          while (ti.hasNext()) {
            final String token = ti.nextToken();
            if (HTTP.CONN_CLOSE.equalsIgnoreCase(token)) {
              return false;
            } else if (HTTP.CONN_KEEP_ALIVE.equalsIgnoreCase(token)) {
              // continue the loop, there may be a "close"
              // afterwards
              keepalive = true;
            }
          }
          if (keepalive) return true;
          // neither "close" nor "keep-alive", use default policy

        } catch (ParseException px) {
          // invalid connection header means no persistent connection
          // we don't have logging in HttpCore, so the exception is
          // lost
          return false;
        }
      }

      // default since HTTP/1.1 is persistent, before it was
      // non-persistent
      return !ver.lessEquals(HttpVersion.HTTP_1_0);
    }
  /**
   * Handles receives one HTTP request over the given connection within the given execution context
   * and sends a response back to the client.
   *
   * @param conn the active connection to the client
   * @param context the actual execution context.
   * @throws IOException in case of an I/O error.
   * @throws HttpException in case of HTTP protocol violation or a processing problem.
   */
  public void handleRequest(final HttpServerConnection conn, final HttpContext context)
      throws IOException, HttpException {

    context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);

    HttpResponse response = null;

    try {

      HttpRequest request = conn.receiveRequestHeader();
      request.setParams(new DefaultedHttpParams(request.getParams(), this.params));

      ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
      if (!ver.lessEquals(HttpVersion.HTTP_1_1)) {
        // Downgrade protocol version if greater than HTTP/1.1
        ver = HttpVersion.HTTP_1_1;
      }

      if (request instanceof HttpEntityEnclosingRequest) {

        if (((HttpEntityEnclosingRequest) request).expectContinue()) {
          response = this.responseFactory.newHttpResponse(ver, HttpStatus.SC_CONTINUE, context);
          response.setParams(new DefaultedHttpParams(response.getParams(), this.params));

          if (this.expectationVerifier != null) {
            try {
              this.expectationVerifier.verify(request, response, context);
            } catch (HttpException ex) {
              response =
                  this.responseFactory.newHttpResponse(
                      HttpVersion.HTTP_1_0, HttpStatus.SC_INTERNAL_SERVER_ERROR, context);
              response.setParams(new DefaultedHttpParams(response.getParams(), this.params));
              handleException(ex, response);
            }
          }
          if (response.getStatusLine().getStatusCode() < 200) {
            // Send 1xx response indicating the server expections
            // have been met
            conn.sendResponseHeader(response);
            conn.flush();
            response = null;
            conn.receiveRequestEntity((HttpEntityEnclosingRequest) request);
          }
        } else {
          conn.receiveRequestEntity((HttpEntityEnclosingRequest) request);
        }
      }

      if (response == null) {
        response = this.responseFactory.newHttpResponse(ver, HttpStatus.SC_OK, context);
        response.setParams(new DefaultedHttpParams(response.getParams(), this.params));

        context.setAttribute(ExecutionContext.HTTP_REQUEST, request);
        context.setAttribute(ExecutionContext.HTTP_RESPONSE, response);

        this.processor.process(request, context);
        doService(request, response, context);
      }

      // Make sure the request content is fully consumed
      if (request instanceof HttpEntityEnclosingRequest) {
        HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
        if (entity != null) {
          entity.consumeContent();
        }
      }

    } catch (HttpException ex) {
      response =
          this.responseFactory.newHttpResponse(
              HttpVersion.HTTP_1_0, HttpStatus.SC_INTERNAL_SERVER_ERROR, context);
      response.setParams(new DefaultedHttpParams(response.getParams(), this.params));
      handleException(ex, response);
    }

    this.processor.process(response, context);
    conn.sendResponseHeader(response);
    conn.sendResponseEntity(response);
    conn.flush();

    if (!this.connStrategy.keepAlive(response, context)) {
      conn.close();
    }
  }