public static String perConnectionAuthorizationHeader(
      Request request, ProxyServer proxyServer, Realm realm) {
    String authorizationHeader = null;

    if (realm != null && realm.getUsePreemptiveAuth()) {
      switch (realm.getScheme()) {
        case NTLM:
          String msg = NtlmEngine.INSTANCE.generateType1Msg();
          authorizationHeader = "NTLM " + msg;
          break;
        case KERBEROS:
        case SPNEGO:
          String host;
          if (proxyServer != null) host = proxyServer.getHost();
          else if (request.getVirtualHost() != null) host = request.getVirtualHost();
          else host = request.getUri().getHost();

          authorizationHeader = "Negotiate " + SpnegoEngine.instance().generateToken(host);
          break;
        default:
          break;
      }
    }

    return authorizationHeader;
  }
Esempio n. 2
0
  @Test(groups = "standalone")
  public void testNonProxyHost() {

    // // should avoid, it's in non-proxy hosts
    Request req = get("http://somewhere.com/foo").build();
    ProxyServer proxyServer = proxyServer("foo", 1234).setNonProxyHost("somewhere.com").build();
    assertTrue(proxyServer.isIgnoredForHost(req.getUri().getHost()));
    //
    // // should avoid, it's in non-proxy hosts (with "*")
    req = get("http://sub.somewhere.com/foo").build();
    proxyServer = proxyServer("foo", 1234).setNonProxyHost("*.somewhere.com").build();
    assertTrue(proxyServer.isIgnoredForHost(req.getUri().getHost()));

    // should use it
    req = get("http://sub.somewhere.com/foo").build();
    proxyServer = proxyServer("foo", 1234).setNonProxyHost("*.somewhere.com").build();
    assertTrue(proxyServer.isIgnoredForHost(req.getUri().getHost()));
  }
  private void kerberosChallenge(
      Channel channel, //
      List<String> authHeaders, //
      Request request, //
      HttpHeaders headers, //
      Realm realm, //
      NettyResponseFuture<?> future)
      throws SpnegoEngineException {

    Uri uri = request.getUri();
    String host = request.getVirtualHost() == null ? uri.getHost() : request.getVirtualHost();
    String challengeHeader = SpnegoEngine.instance().generateToken(host);
    headers.set(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader);
  }
  private boolean exitAfterHandlingConnect( //
      final Channel channel, //
      final NettyResponseFuture<?> future, //
      final Request request, //
      ProxyServer proxyServer, //
      int statusCode, //
      HttpRequest httpRequest)
      throws IOException {

    if (future.isKeepAlive()) future.attachChannel(channel, true);

    Uri requestUri = request.getUri();
    logger.debug("Connecting to proxy {} for scheme {}", proxyServer, requestUri.getScheme());

    channelManager.upgradeProtocol(channel.pipeline(), requestUri);
    future.setReuseChannel(true);
    future.setConnectAllowed(false);
    requestSender.drainChannelAndExecuteNextRequest(
        channel, future, new RequestBuilder(future.getTargetRequest()).build());

    return true;
  }
  private boolean exitAfterHandling407( //
      Channel channel, //
      NettyResponseFuture<?> future, //
      HttpResponse response, //
      Request request, //
      int statusCode, //
      ProxyServer proxyServer, //
      HttpRequest httpRequest) {

    if (future.getInProxyAuth().getAndSet(true)) {
      logger.info("Can't handle 407 as auth was already performed");
      return false;
    }

    Realm proxyRealm = future.getProxyRealm();

    if (proxyRealm == null) {
      logger.info("Can't handle 407 as there's no proxyRealm");
      return false;
    }

    List<String> proxyAuthHeaders = response.headers().getAll(HttpHeaders.Names.PROXY_AUTHENTICATE);

    if (proxyAuthHeaders.isEmpty()) {
      logger.info("Can't handle 407 as response doesn't contain Proxy-Authenticate headers");
      return false;
    }

    // FIXME what's this???
    future.setChannelState(ChannelState.NEW);
    HttpHeaders requestHeaders = new DefaultHttpHeaders(false).add(request.getHeaders());

    switch (proxyRealm.getScheme()) {
      case BASIC:
        if (getHeaderWithPrefix(proxyAuthHeaders, "Basic") == null) {
          logger.info(
              "Can't handle 407 with Basic realm as Proxy-Authenticate headers don't match");
          return false;
        }

        if (proxyRealm.isUsePreemptiveAuth()) {
          // FIXME do we need this, as future.getAndSetAuth
          // was tested above?
          // auth was already performed, most likely auth
          // failed
          logger.info(
              "Can't handle 407 with Basic realm as auth was preemptive and already performed");
          return false;
        }

        // FIXME do we want to update the realm, or directly
        // set the header?
        Realm newBasicRealm =
            realm(proxyRealm) //
                .setUsePreemptiveAuth(true) //
                .build();
        future.setProxyRealm(newBasicRealm);
        break;

      case DIGEST:
        String digestHeader = getHeaderWithPrefix(proxyAuthHeaders, "Digest");
        if (digestHeader == null) {
          logger.info(
              "Can't handle 407 with Digest realm as Proxy-Authenticate headers don't match");
          return false;
        }
        Realm newDigestRealm =
            realm(proxyRealm) //
                .setUri(request.getUri()) //
                .setMethodName(request.getMethod()) //
                .setUsePreemptiveAuth(true) //
                .parseProxyAuthenticateHeader(digestHeader) //
                .build();
        future.setProxyRealm(newDigestRealm);
        break;

      case NTLM:
        String ntlmHeader = getHeaderWithPrefix(proxyAuthHeaders, "NTLM");
        if (ntlmHeader == null) {
          logger.info("Can't handle 407 with NTLM realm as Proxy-Authenticate headers don't match");
          return false;
        }
        ntlmProxyChallenge(ntlmHeader, request, proxyRealm, requestHeaders, future);
        Realm newNtlmRealm =
            realm(proxyRealm) //
                .setUsePreemptiveAuth(true) //
                .build();
        future.setProxyRealm(newNtlmRealm);
        break;

      case KERBEROS:
      case SPNEGO:
        if (getHeaderWithPrefix(proxyAuthHeaders, "Negociate") == null) {
          logger.info(
              "Can't handle 407 with Kerberos or Spnego realm as Proxy-Authenticate headers don't match");
          return false;
        }
        try {
          kerberosProxyChallenge(
              channel, proxyAuthHeaders, request, proxyServer, proxyRealm, requestHeaders, future);

        } catch (SpnegoEngineException e) {
          // FIXME
          String ntlmHeader2 = getHeaderWithPrefix(proxyAuthHeaders, "NTLM");
          if (ntlmHeader2 != null) {
            logger.warn("Kerberos/Spnego proxy auth failed, proceeding with NTLM");
            ntlmChallenge(ntlmHeader2, request, requestHeaders, proxyRealm, future);
            Realm newNtlmRealm2 =
                realm(proxyRealm) //
                    .setScheme(AuthScheme.NTLM) //
                    .setUsePreemptiveAuth(true) //
                    .build();
            future.setProxyRealm(newNtlmRealm2);
          } else {
            requestSender.abort(channel, future, e);
            return false;
          }
        }
        break;
      default:
        throw new IllegalStateException("Invalid Authentication scheme " + proxyRealm.getScheme());
    }

    RequestBuilder nextRequestBuilder =
        new RequestBuilder(future.getCurrentRequest()).setHeaders(requestHeaders);
    if (future.getCurrentRequest().getUri().isSecured()) {
      nextRequestBuilder.setMethod(CONNECT);
    }
    final Request nextRequest = nextRequestBuilder.build();

    logger.debug("Sending proxy authentication to {}", request.getUri());
    if (future.isKeepAlive() //
        && HttpHeaders.isKeepAlive(httpRequest) //
        && HttpHeaders.isKeepAlive(response) //
        // support broken Proxy-Connection
        && !response.headers().contains("Proxy-Connection", HttpHeaders.Values.CLOSE, true) //
        && !HttpHeaders.isTransferEncodingChunked(httpRequest) //
        && !HttpHeaders.isTransferEncodingChunked(response)) {
      future.setConnectAllowed(true);
      future.setReuseChannel(true);
      requestSender.drainChannelAndExecuteNextRequest(channel, future, nextRequest);
    } else {
      channelManager.closeChannel(channel);
      requestSender.sendNextRequest(nextRequest, future);
    }

    return true;
  }
  public NettyRequest newNettyRequest(
      Request request, boolean forceConnect, ProxyServer proxyServer) throws IOException {

    Uri uri = request.getUri();
    HttpMethod method = forceConnect ? HttpMethod.CONNECT : HttpMethod.valueOf(request.getMethod());
    boolean connect = method == HttpMethod.CONNECT;

    boolean allowConnectionPooling =
        config.isAllowPoolingConnections()
            && (!HttpUtils.isSecure(uri) || config.isAllowPoolingSslConnections());

    HttpVersion httpVersion =
        !allowConnectionPooling || (connect && proxyServer.isForceHttp10())
            ? HttpVersion.HTTP_1_0
            : HttpVersion.HTTP_1_1;
    String requestUri = requestUri(uri, proxyServer, connect);

    NettyBody body = body(request, connect);

    HttpRequest httpRequest;
    NettyRequest nettyRequest;
    if (body instanceof NettyDirectBody) {
      ChannelBuffer buffer = NettyDirectBody.class.cast(body).channelBuffer();
      httpRequest = new DefaultHttpRequest(httpVersion, method, requestUri);
      // body is passed as null as it's written directly with the request
      httpRequest.setContent(buffer);
      nettyRequest = new NettyRequest(httpRequest, null);

    } else {
      httpRequest = new DefaultHttpRequest(httpVersion, method, requestUri);
      nettyRequest = new NettyRequest(httpRequest, body);
    }

    HttpHeaders headers = httpRequest.headers();

    if (!connect) {
      // assign headers as configured on request
      for (Entry<String, List<String>> header : request.getHeaders()) {
        headers.set(header.getKey(), header.getValue());
      }

      if (isNonEmpty(request.getCookies()))
        headers.set(COOKIE, CookieEncoder.encode(request.getCookies()));

      if (config.isCompressionEnforced() && !headers.contains(ACCEPT_ENCODING))
        headers.set(ACCEPT_ENCODING, GZIP_DEFLATE);
    }

    if (body != null) {
      if (body.getContentLength() < 0) headers.set(TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
      else headers.set(CONTENT_LENGTH, body.getContentLength());

      if (body.getContentType() != null) headers.set(CONTENT_TYPE, body.getContentType());
    }

    // connection header and friends
    boolean webSocket = isWebSocket(uri.getScheme());
    if (!connect && webSocket) {
      String origin =
          "http://"
              + uri.getHost()
              + ":"
              + (uri.getPort() == -1 ? isSecure(uri.getScheme()) ? 443 : 80 : uri.getPort());
      headers
          .set(UPGRADE, HttpHeaders.Values.WEBSOCKET) //
          .set(CONNECTION, HttpHeaders.Values.UPGRADE) //
          .set(ORIGIN, origin) //
          .set(SEC_WEBSOCKET_KEY, getKey()) //
          .set(SEC_WEBSOCKET_VERSION, "13");

    } else if (!headers.contains(CONNECTION)) {
      String connectionHeaderValue =
          connectionHeader(allowConnectionPooling, httpVersion == HttpVersion.HTTP_1_1);
      if (connectionHeaderValue != null) headers.set(CONNECTION, connectionHeaderValue);
    }

    if (!headers.contains(HOST)) headers.set(HOST, hostHeader(request, uri));

    Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm();

    // don't override authorization but append
    addAuthorizationHeader(headers, systematicAuthorizationHeader(request, realm));

    setProxyAuthorizationHeader(
        headers, systematicProxyAuthorizationHeader(request, proxyServer, realm, connect));

    // Add default accept headers
    if (!headers.contains(ACCEPT)) headers.set(ACCEPT, "*/*");

    // Add default user agent
    if (!headers.contains(USER_AGENT) && config.getUserAgent() != null)
      headers.set(USER_AGENT, config.getUserAgent());

    return nettyRequest;
  }
  protected boolean exitAfterHandlingRedirect( //
      Channel channel, //
      NettyResponseFuture<?> future, //
      HttpResponse response, //
      Request request, //
      int statusCode)
      throws Exception {

    if (followRedirect(config, request) && REDIRECT_STATUSES.contains(statusCode)) {
      if (future.incrementAndGetCurrentRedirectCount() >= config.getMaxRedirects()) {
        throw new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects());

      } else {
        // We must allow 401 handling again.
        future.getAndSetAuth(false);

        HttpHeaders responseHeaders = response.headers();
        String location = responseHeaders.get(HttpHeaders.Names.LOCATION);
        Uri uri = Uri.create(future.getUri(), location);

        if (!uri.equals(future.getUri())) {
          final RequestBuilder requestBuilder = new RequestBuilder(future.getRequest());

          if (!config.isRemoveQueryParamOnRedirect())
            requestBuilder.addQueryParams(future.getRequest().getQueryParams());

          // if we are to strictly handle 302, we should keep the original method (which browsers
          // don't)
          // 303 must force GET
          if ((statusCode == FOUND.code() && !config.isStrict302Handling())
              || statusCode == SEE_OTHER.code()) requestBuilder.setMethod("GET");

          // in case of a redirect from HTTP to HTTPS, future attributes might change
          final boolean initialConnectionKeepAlive = future.isKeepAlive();
          final String initialPoolKey = channelManager.getPartitionId(future);

          future.setUri(uri);
          String newUrl = uri.toUrl();
          if (request.getUri().getScheme().startsWith(WEBSOCKET)) {
            newUrl = newUrl.replaceFirst(HTTP, WEBSOCKET);
          }

          logger.debug("Redirecting to {}", newUrl);

          for (String cookieStr : responseHeaders.getAll(HttpHeaders.Names.SET_COOKIE)) {
            Cookie c = CookieDecoder.decode(cookieStr, timeConverter);
            if (c != null) requestBuilder.addOrReplaceCookie(c);
          }

          Callback callback =
              channelManager.newDrainCallback(
                  future, channel, initialConnectionKeepAlive, initialPoolKey);

          if (HttpHeaders.isTransferEncodingChunked(response)) {
            // We must make sure there is no bytes left before
            // executing the next request.
            // FIXME investigate this
            Channels.setAttribute(channel, callback);
          } else {
            // FIXME don't understand: this offers the connection to the pool, or even closes it,
            // while the
            // request has not been sent, right?
            callback.call();
          }

          Request redirectRequest = requestBuilder.setUrl(newUrl).build();
          // FIXME why not reuse the channel is same host?
          requestSender.sendNextRequest(redirectRequest, future);
          return true;
        }
      }
    }
    return false;
  }