private Realm kerberosChallenge(
      List<String> proxyAuth,
      Request request,
      ProxyServer proxyServer,
      FluentCaseInsensitiveStringsMap headers,
      Realm realm,
      NettyResponseFuture<?> future)
      throws NTLMEngineException {

    URI uri = request.getURI();
    String host =
        request.getVirtualHost() == null
            ? AsyncHttpProviderUtils.getHost(uri)
            : request.getVirtualHost();
    String server = proxyServer == null ? host : proxyServer.getHost();
    try {
      String challengeHeader = SpnegoEngine.instance().generateToken(server);
      headers.remove(HttpHeaders.Names.AUTHORIZATION);
      headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader);

      return newRealmBuilder(realm) //
          .setUri(uri.getRawPath()) //
          .setMethodName(request.getMethod()) //
          .setScheme(Realm.AuthScheme.KERBEROS) //
          .build();

    } catch (Throwable throwable) {
      if (isNTLM(proxyAuth)) {
        return ntlmChallenge(proxyAuth, request, proxyServer, headers, realm, future);
      }
      channels.abort(future, throwable);
      return null;
    }
  }
  @Test
  public void testGetWithRequestBuilder() throws UnsupportedEncodingException {
    ConsumerKey consumer = new ConsumerKey(CONSUMER_KEY, CONSUMER_SECRET);
    RequestToken user = new RequestToken(TOKEN_KEY, TOKEN_SECRET);
    OAuthSignatureCalculator calc =
        new StaticOAuthSignatureCalculator(consumer, user, TIMESTAMP, NONCE);

    final Request req =
        get("http://photos.example.net/photos") //
            .addQueryParam("file", "vacation.jpg") //
            .addQueryParam("size", "original") //
            .setSignatureCalculator(calc) //
            .build();

    final List<Param> params = req.getQueryParams();
    assertEquals(params.size(), 2);

    // From the signature tester, the URL should look like:
    // normalized parameters:
    // file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original
    // signature base string:
    // GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal
    // signature: tR3+Ty81lMeYAr/Fid0kMTYa/WM=
    // Authorization header: OAuth
    // realm="",oauth_version="1.0",oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_signature_method="HMAC-SHA1",oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D"

    String authHeader = req.getHeaders().get(AUTHORIZATION);
    Matcher m = Pattern.compile("oauth_signature=\"(.+?)\"").matcher(authHeader);
    assertEquals(m.find(), true);
    String encodedSig = m.group(1);
    String sig = URLDecoder.decode(encodedSig, "UTF-8");

    assertEquals(sig, "tR3+Ty81lMeYAr/Fid0kMTYa/WM=");
    assertEquals(req.getUrl(), "http://photos.example.net/photos?file=vacation.jpg&size=original");
  }
  private Realm ntlmProxyChallenge(
      List<String> wwwAuth,
      Request request,
      ProxyServer proxyServer,
      FluentCaseInsensitiveStringsMap headers,
      Realm realm,
      NettyResponseFuture<?> future)
      throws NTLMEngineException {
    future.getAndSetAuth(false);
    headers.remove(HttpHeaders.Names.PROXY_AUTHORIZATION);

    addType3NTLMAuthorizationHeader(
        wwwAuth,
        headers,
        proxyServer.getPrincipal(),
        proxyServer.getPassword(),
        proxyServer.getNtlmDomain(),
        proxyServer.getHost());

    return newRealmBuilder(realm) //
        // .setScheme(realm.getAuthScheme())
        .setUri(request.getURI().getPath()) //
        .setMethodName(request.getMethod())
        .build();
  }
  private boolean handleConnectOKAndExit(
      int statusCode,
      Realm realm,
      final Request request,
      HttpRequest httpRequest,
      HttpResponse response,
      final NettyResponseFuture<?> future,
      ProxyServer proxyServer,
      final Channel channel)
      throws IOException {
    if (statusCode == OK.code() && httpRequest.getMethod() == HttpMethod.CONNECT) {

      LOGGER.debug("Connected to {}:{}", proxyServer.getHost(), proxyServer.getPort());

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

      try {
        LOGGER.debug("Connecting to proxy {} for scheme {}", proxyServer, request.getUrl());
        channels.upgradeProtocol(channel.pipeline(), request.getURI().getScheme());
      } catch (Throwable ex) {
        channels.abort(future, ex);
      }
      future.setReuseChannel(true);
      future.setConnectAllowed(false);
      requestSender.sendNextRequest(new RequestBuilder(future.getRequest()).build(), future);
      return true;
    }

    return false;
  }
  @Test
  public void testPostCalculateSignature() throws UnsupportedEncodingException {
    ConsumerKey consumer = new ConsumerKey(CONSUMER_KEY, CONSUMER_SECRET);
    RequestToken user = new RequestToken(TOKEN_KEY, TOKEN_SECRET);
    OAuthSignatureCalculator calc =
        new StaticOAuthSignatureCalculator(consumer, user, TIMESTAMP, NONCE);

    final Request req =
        post("http://photos.example.net/photos") //
            .addFormParam("file", "vacation.jpg") //
            .addFormParam("size", "original") //
            .setSignatureCalculator(calc) //
            .build();

    // From the signature tester, POST should look like:
    // normalized parameters:
    // file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original
    // signature base string:
    // POST&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal
    // signature: wPkvxykrw+BTdCcGqKr+3I+PsiM=
    // header: OAuth
    // realm="",oauth_version="1.0",oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_signature_method="HMAC-SHA1",oauth_signature="wPkvxykrw%2BBTdCcGqKr%2B3I%2BPsiM%3D"

    String authHeader = req.getHeaders().get(AUTHORIZATION);
    Matcher m = Pattern.compile("oauth_signature=\"(.+?)\"").matcher(authHeader);
    assertEquals(m.find(), true);
    String encodedSig = m.group(1);
    String sig = URLDecoder.decode(encodedSig, "UTF-8");

    assertEquals(sig, "wPkvxykrw+BTdCcGqKr+3I+PsiM=");
  }
  private boolean handleResponseAndExit(
      final Channel channel,
      final NettyResponseFuture<?> future,
      AsyncHandler<?> handler,
      HttpRequest httpRequest,
      ProxyServer proxyServer,
      HttpResponse response)
      throws Exception {

    // store the original headers so we can re-send all them to
    // the handler in case of trailing headers
    future.setHttpHeaders(response.headers());

    future.setKeepAlive(
        !HttpHeaders.Values.CLOSE.equalsIgnoreCase(
            response.headers().get(HttpHeaders.Names.CONNECTION)));

    HttpResponseStatus status = new ResponseStatus(future.getURI(), response, config);
    int statusCode = response.getStatus().code();
    Request request = future.getRequest();
    Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm();
    HttpResponseHeaders responseHeaders = new ResponseHeaders(future.getURI(), response.headers());

    return handleResponseFiltersReplayRequestAndExit(channel, future, status, responseHeaders) //
        || handleUnauthorizedAndExit(
            statusCode, realm, request, response, future, proxyServer, channel) //
        || handleContinueAndExit(channel, future, statusCode) //
        || handleProxyAuthenticationRequiredAndExit(
            statusCode, realm, request, response, future, proxyServer)
        || handleConnectOKAndExit(
            statusCode, realm, request, httpRequest, response, future, proxyServer, channel) //
        || handleRedirectAndExit(request, future, response, channel) //
        || handleHanderAndExit(channel, future, handler, status, responseHeaders, response);
  }
  private boolean exitAfterSpecialCases(
      final HttpResponse response, final Channel channel, final NettyResponseFuture<?> future)
      throws Exception {

    HttpRequest httpRequest = future.getNettyRequest().getHttpRequest();
    ProxyServer proxyServer = future.getProxyServer();
    int statusCode = response.getStatus().code();
    Request request = future.getCurrentRequest();
    Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm();

    if (statusCode == UNAUTHORIZED_401) {
      return exitAfterHandling401(
          channel, future, response, request, statusCode, realm, proxyServer, httpRequest);

    } else if (statusCode == PROXY_AUTHENTICATION_REQUIRED_407) {
      return exitAfterHandling407(
          channel, future, response, request, statusCode, proxyServer, httpRequest);

    } else if (statusCode == CONTINUE_100) {
      return exitAfterHandling100(channel, future, statusCode);

    } else if (REDIRECT_STATUSES.contains(statusCode)) {
      return exitAfterHandlingRedirect(channel, future, response, request, statusCode, realm);

    } else if (httpRequest.getMethod() == HttpMethod.CONNECT && statusCode == OK_200) {
      return exitAfterHandlingConnect(
          channel, future, request, proxyServer, statusCode, httpRequest);
    }
    return false;
  }
  private void addGeneralHeaders(final Request request, final HttpRequestPacket requestPacket) {

    if (request.hasHeaders()) {
      final FluentCaseInsensitiveStringsMap map = request.getHeaders();
      for (final Map.Entry<String, List<String>> entry : map.entrySet()) {
        final String headerName = entry.getKey();
        final List<String> headerValues = entry.getValue();
        if (isNonEmpty(headerValues)) {
          for (int i = 0, len = headerValues.size(); i < len; i++) {
            requestPacket.addHeader(headerName, headerValues.get(i));
          }
        }
      }
    }

    final MimeHeaders headers = requestPacket.getHeaders();
    if (!headers.contains(Header.Connection)) {
      // final boolean canCache = context.provider.clientConfig.getAllowPoolingConnection();
      requestPacket.addHeader(Header.Connection, /*(canCache ? */ "keep-alive" /*: "close")*/);
    }

    if (!headers.contains(Header.Accept)) {
      requestPacket.addHeader(Header.Accept, "*/*");
    }

    if (!headers.contains(Header.UserAgent)) {
      requestPacket.addHeader(Header.UserAgent, config.getUserAgent());
    }
  }
  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;
  }
  private boolean handleProxyAuthenticationRequiredAndExit(
      int statusCode,
      Realm realm,
      final Request request,
      HttpResponse response,
      final NettyResponseFuture<?> future,
      ProxyServer proxyServer)
      throws Exception {
    if (statusCode == PROXY_AUTHENTICATION_REQUIRED.code() && realm != null) {
      List<String> proxyAuthenticateHeaders =
          response.headers().getAll(HttpHeaders.Names.PROXY_AUTHENTICATE);
      if (!proxyAuthenticateHeaders.isEmpty() && !future.getAndSetAuth(true)) {
        LOGGER.debug("Sending proxy authentication to {}", request.getUrl());

        future.setState(NettyResponseFuture.STATE.NEW);
        Realm newRealm = null;

        boolean negociate = proxyAuthenticateHeaders.contains("Negotiate");
        if (!proxyAuthenticateHeaders.contains("Kerberos")
            && (isNTLM(proxyAuthenticateHeaders) || negociate)) {
          newRealm =
              ntlmProxyChallenge(
                  proxyAuthenticateHeaders,
                  request,
                  proxyServer,
                  request.getHeaders(),
                  realm,
                  future);
          // SPNEGO KERBEROS
        } else if (negociate) {
          newRealm =
              kerberosChallenge(
                  proxyAuthenticateHeaders,
                  request,
                  proxyServer,
                  request.getHeaders(),
                  realm,
                  future);
          if (newRealm == null) return true;
        } else {
          newRealm = future.getRequest().getRealm();
        }

        future.setReuseChannel(true);
        future.setConnectAllowed(true);
        requestSender.sendNextRequest(
            new RequestBuilder(future.getRequest())
                .setHeaders(request.getHeaders())
                .setRealm(newRealm)
                .build(),
            future);
        return true;
      }
    }
    return false;
  }
  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 static void addQueryString(final Request request, final HttpRequestPacket requestPacket) {

    final FluentStringsMap map = request.getQueryParams();
    if (isNonEmpty(map)) {
      StringBuilder sb = new StringBuilder(128);
      for (final Map.Entry<String, List<String>> entry : map.entrySet()) {
        final String name = entry.getKey();
        final List<String> values = entry.getValue();
        if (isNonEmpty(values)) {
          try {
            for (int i = 0, len = values.size(); i < len; i++) {
              final String value = values.get(i);
              if (isNonEmpty(value)) {
                sb.append(URLEncoder.encode(name, "UTF-8"))
                    .append('=')
                    .append(URLEncoder.encode(values.get(i), "UTF-8"))
                    .append('&');
              } else {
                sb.append(URLEncoder.encode(name, "UTF-8")).append('&');
              }
            }
          } catch (UnsupportedEncodingException ignored) {
          }
        }
      }
      sb.setLength(sb.length() - 1);
      String queryString = sb.toString();

      requestPacket.setQueryString(queryString);
    }
  }
 private static void initTransferCompletionHandler(final Request request, final AsyncHandler h)
     throws IOException {
   if (h instanceof TransferCompletionHandler) {
     final FluentCaseInsensitiveStringsMap map =
         new FluentCaseInsensitiveStringsMap(request.getHeaders());
     TransferCompletionHandler.class.cast(h).headers(map);
   }
 }
Example #14
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 addCookies(final Request request, final HttpRequestPacket requestPacket) {

    final Collection<Cookie> cookies = request.getCookies();
    if (isNonEmpty(cookies)) {
      StringBuilder sb = new StringBuilder(128);
      org.glassfish.grizzly.http.Cookie[] gCookies =
          new org.glassfish.grizzly.http.Cookie[cookies.size()];
      convertCookies(cookies, gCookies);
      CookieSerializerUtils.serializeClientCookies(
          sb, false, config.isRfc6265CookieEncoding(), gCookies);
      requestPacket.addHeader(Header.Cookie, sb.toString());
    }
  }
 private static void addHostHeader(
     final Request request, final URI uri, final HttpRequestPacket requestPacket) {
   String host = request.getVirtualHost();
   if (host != null) {
     requestPacket.addHeader(Header.Host, host);
   } else {
     if (uri.getPort() == -1) {
       requestPacket.addHeader(Header.Host, uri.getHost());
     } else {
       requestPacket.addHeader(Header.Host, uri.getHost() + ':' + uri.getPort());
     }
   }
 }
  private Realm ntlmChallenge(
      List<String> wwwAuth,
      Request request,
      ProxyServer proxyServer,
      FluentCaseInsensitiveStringsMap headers,
      Realm realm,
      NettyResponseFuture<?> future)
      throws NTLMEngineException {

    boolean useRealm = proxyServer == null && realm != null;

    String ntlmDomain = useRealm ? realm.getNtlmDomain() : proxyServer.getNtlmDomain();
    String ntlmHost = useRealm ? realm.getNtlmHost() : proxyServer.getHost();
    String principal = useRealm ? realm.getPrincipal() : proxyServer.getPrincipal();
    String password = useRealm ? realm.getPassword() : proxyServer.getPassword();

    if (realm != null && !realm.isNtlmMessageType2Received()) {
      String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg(ntlmDomain, ntlmHost);

      URI uri = request.getURI();
      addNTLMAuthorizationHeader(headers, challengeHeader);
      future.getAndSetAuth(false);
      return newRealmBuilder(realm) //
          .setScheme(realm.getAuthScheme()) //
          .setUri(uri.getRawPath()) //
          .setMethodName(request.getMethod()) //
          .setNtlmMessageType2Received(true) //
          .build();

    } else {
      addType3NTLMAuthorizationHeader(wwwAuth, headers, principal, password, ntlmDomain, ntlmHost);
      Realm.AuthScheme authScheme = realm != null ? realm.getAuthScheme() : Realm.AuthScheme.NTLM;
      return newRealmBuilder(realm) //
          .setScheme(authScheme) //
          .setUri(request.getURI().getPath()) //
          .setMethodName(request.getMethod()) //
          .build();
    }
  }
  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;
  }
  @SuppressWarnings("unchecked")
  public boolean sendRequest(
      final FilterChainContext ctx, final Request request, final HttpRequestPacket requestPacket)
      throws IOException {

    boolean isWriteComplete = true;

    if (Utils.requestHasEntityBody(request)) {
      final HttpTxContext context = HttpTxContext.get(ctx);
      BodyHandler handler = bodyHandlerFactory.getBodyHandler(request);
      if (requestPacket.getHeaders().contains(Header.Expect)
          && requestPacket.getHeaders().getValue(1).equalsIgnoreCase("100-Continue")) {
        // We have to set the content-length now as the headers will be flushed
        // before the FileBodyHandler is invoked.  If we don't do it here, and
        // the user didn't explicitly set the length, then the transfer-encoding
        // will be chunked and zero-copy file transfer will not occur.
        final File f = request.getFile();
        if (f != null) {
          requestPacket.setContentLengthLong(f.length());
        }
        handler = new ExpectHandler(handler);
      }
      context.setBodyHandler(handler);
      if (logger.isDebugEnabled()) {
        logger.debug("REQUEST: {}", requestPacket);
      }
      isWriteComplete = handler.doHandle(ctx, request, requestPacket);
    } else {
      HttpContent content = HttpContent.builder(requestPacket).last(true).build();
      if (logger.isDebugEnabled()) {
        logger.debug("REQUEST: {}", requestPacket);
      }
      ctx.write(content, ctx.getTransportContext().getCompletionHandler());
    }

    return isWriteComplete;
  }
  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;
  }
 protected List<String> getProxyAuthorizationHeader(Request request) {
   return request.getHeaders().get(PROXY_AUTHORIZATION);
 }
  private boolean sendAsGrizzlyRequest(
      final RequestInfoHolder requestInfoHolder, final FilterChainContext ctx) throws IOException {

    HttpTxContext httpTxContext = requestInfoHolder.getHttpTxContext();
    if (httpTxContext == null) {
      httpTxContext = HttpTxContext.create(requestInfoHolder);
    }

    if (checkProxyAuthFailure(ctx, httpTxContext)) {
      return true;
    }

    final Request request = httpTxContext.getRequest();
    final URI uri = request.isUseRawUrl() ? request.getRawURI() : request.getURI();
    boolean secure = Utils.isSecure(uri);

    // If the request is secure, check to see if an error occurred during
    // the handshake.  We have to do this here, as the error would occur
    // out of the scope of a HttpTxContext so there would be
    // no good way to communicate the problem to the caller.
    if (secure && checkHandshakeError(ctx, httpTxContext)) {
      return true;
    }

    if (isUpgradeRequest(httpTxContext.getHandler())
        && isWSRequest(httpTxContext.getRequestUrl())) {
      httpTxContext.setWSRequest(true);
      convertToUpgradeRequest(httpTxContext);
    }

    HttpRequestPacket requestPacket = requestCache.poll();
    if (requestPacket == null) {
      requestPacket = new HttpRequestPacketImpl();
    }
    requestPacket.setMethod(request.getMethod());
    requestPacket.setProtocol(Protocol.HTTP_1_1);

    // Special handling for CONNECT.
    if (Method.CONNECT.matchesMethod(request.getMethod())) {
      final int port = uri.getPort();
      requestPacket.setRequestURI(uri.getHost() + ':' + (port == -1 ? 443 : port));
    } else {
      requestPacket.setRequestURI(uri.getPath());
    }

    if (Utils.requestHasEntityBody(request)) {
      final long contentLength = request.getContentLength();
      if (contentLength >= 0) {
        requestPacket.setContentLengthLong(contentLength);
        requestPacket.setChunked(false);
      } else {
        requestPacket.setChunked(true);
      }
    }

    if (httpTxContext.isWSRequest()) {
      try {
        final URI wsURI = new URI(httpTxContext.getWsRequestURI());
        httpTxContext.setProtocolHandler(Version.RFC6455.createHandler(true));
        httpTxContext.setHandshake(httpTxContext.getProtocolHandler().createHandShake(wsURI));
        requestPacket =
            (HttpRequestPacket) httpTxContext.getHandshake().composeHeaders().getHttpHeader();
      } catch (URISyntaxException e) {
        throw new IllegalArgumentException("Invalid WS URI: " + httpTxContext.getWsRequestURI());
      }
    }

    requestPacket.setSecure(secure);
    addQueryString(request, requestPacket);
    addHostHeader(request, uri, requestPacket);
    addGeneralHeaders(request, requestPacket);
    addCookies(request, requestPacket);

    initTransferCompletionHandler(request, httpTxContext.getHandler());

    final HttpRequestPacket requestPacketLocal = requestPacket;
    FilterChainContext sendingCtx = ctx;

    if (secure) {
      // Check to see if the ProtocolNegotiator has given
      // us a different FilterChain to use.  If so, we need
      // use a different FilterChainContext when invoking sendRequest().
      sendingCtx = checkAndHandleFilterChainUpdate(ctx, sendingCtx);
    }
    final Connection c = ctx.getConnection();
    if (!Utils.isSpdyConnection(c)) {
      HttpContext.newInstance(ctx, c, c, c);
    } else {
      SpdySession session = SpdySession.get(c);
      final Lock lock = session.getNewClientStreamLock();
      try {
        lock.lock();
        SpdyStream stream =
            session.openStream(
                requestPacketLocal,
                session.getNextLocalStreamId(),
                0,
                0,
                0,
                false,
                !requestPacketLocal.isExpectContent());
        HttpContext.newInstance(ctx, stream, stream, stream);
      } finally {
        lock.unlock();
      }
    }
    HttpTxContext.set(ctx, httpTxContext);
    return sendRequest(sendingCtx, request, requestPacketLocal);
  }
  private NettyBody body(Request request, boolean connect) throws IOException {
    NettyBody nettyBody = null;
    if (!connect) {

      Charset bodyCharset =
          request.getBodyCharset() == null ? DEFAULT_CHARSET : request.getBodyCharset();

      if (request.getByteData() != null) nettyBody = new NettyByteArrayBody(request.getByteData());
      else if (request.getCompositeByteData() != null)
        nettyBody = new NettyCompositeByteArrayBody(request.getCompositeByteData());
      else if (request.getStringData() != null)
        nettyBody =
            new NettyByteBufferBody(
                StringUtils.charSequence2ByteBuffer(request.getStringData(), bodyCharset));
      else if (request.getByteBufferData() != null)
        nettyBody = new NettyByteBufferBody(request.getByteBufferData());
      else if (request.getStreamData() != null)
        nettyBody = new NettyInputStreamBody(request.getStreamData(), config);
      else if (isNonEmpty(request.getFormParams())) {

        String contentType = null;
        if (!request.getHeaders().containsKey(CONTENT_TYPE))
          contentType = HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED;

        nettyBody =
            new NettyByteBufferBody(
                urlEncodeFormParams(request.getFormParams(), bodyCharset), contentType);

      } else if (isNonEmpty(request.getParts()))
        nettyBody = new NettyMultipartBody(request.getParts(), request.getHeaders(), config);
      else if (request.getFile() != null) nettyBody = new NettyFileBody(request.getFile(), config);
      else if (request.getBodyGenerator() instanceof FileBodyGenerator) {
        FileBodyGenerator fileBodyGenerator = (FileBodyGenerator) request.getBodyGenerator();
        nettyBody =
            new NettyFileBody(
                fileBodyGenerator.getFile(),
                fileBodyGenerator.getRegionSeek(),
                fileBodyGenerator.getRegionLength(),
                config);

      } else if (request.getBodyGenerator() instanceof InputStreamBodyGenerator)
        nettyBody =
            new NettyInputStreamBody(
                InputStreamBodyGenerator.class.cast(request.getBodyGenerator()).getInputStream(),
                config);
      else if (request.getBodyGenerator() != null)
        nettyBody = new NettyBodyBody(request.getBodyGenerator().createBody(), config);
    }

    return nettyBody;
  }
  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;
  }
    @Override
    public void call() throws Exception {

      WebSocketUpgradeHandler handler =
          WebSocketUpgradeHandler.class.cast(future.getAsyncHandler());
      Request request = future.getRequest();

      HttpResponseStatus status =
          new NettyResponseStatus(future.getUri(), config, response, channel);
      HttpResponseHeaders responseHeaders = new NettyResponseHeaders(response.headers());
      Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm();

      if (exitAfterProcessingFilters(channel, future, handler, status, responseHeaders)) {
        return;
      }

      future.setHttpHeaders(response.headers());
      if (exitAfterHandlingRedirect(
          channel, future, response, request, response.getStatus().code(), realm)) return;

      boolean validStatus = response.getStatus().equals(SWITCHING_PROTOCOLS);
      boolean validUpgrade = response.headers().get(HttpHeaders.Names.UPGRADE) != null;
      String connection = response.headers().get(HttpHeaders.Names.CONNECTION);
      if (connection == null)
        connection =
            response.headers().get(HttpHeaders.Names.CONNECTION.toLowerCase(Locale.ENGLISH));
      boolean validConnection = HttpHeaders.Values.UPGRADE.equalsIgnoreCase(connection);
      boolean statusReceived = handler.onStatusReceived(status) == State.UPGRADE;

      if (!statusReceived) {
        try {
          handler.onCompleted();
        } finally {
          future.done();
        }
        return;
      }

      final boolean headerOK = handler.onHeadersReceived(responseHeaders) == State.CONTINUE;
      if (!headerOK || !validStatus || !validUpgrade || !validConnection) {
        requestSender.abort(channel, future, new IOException("Invalid handshake response"));
        return;
      }

      String accept = response.headers().get(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT);
      String key =
          getAcceptKey(
              future
                  .getNettyRequest()
                  .getHttpRequest()
                  .headers()
                  .get(HttpHeaders.Names.SEC_WEBSOCKET_KEY));
      if (accept == null || !accept.equals(key)) {
        requestSender.abort(
            channel,
            future,
            new IOException(
                String.format("Invalid challenge. Actual: %s. Expected: %s", accept, key)));
      }

      channelManager.upgradePipelineForWebSockets(channel.pipeline());

      invokeOnSucces(channel, handler);
      future.done();
      // set back the future so the protocol gets notified of frames
      Channels.setAttribute(channel, future);
    }
  private boolean handleUnauthorizedAndExit(
      int statusCode,
      Realm realm,
      final Request request,
      HttpResponse response,
      final NettyResponseFuture<?> future,
      ProxyServer proxyServer,
      final Channel channel)
      throws Exception {
    if (statusCode == UNAUTHORIZED.code() && realm != null) {

      List<String> authenticateHeaders =
          response.headers().getAll(HttpHeaders.Names.WWW_AUTHENTICATE);

      if (!authenticateHeaders.isEmpty() && !future.getAndSetAuth(true)) {
        future.setState(NettyResponseFuture.STATE.NEW);
        Realm newRealm = null;
        // NTLM
        boolean negociate = authenticateHeaders.contains("Negotiate");
        if (!authenticateHeaders.contains("Kerberos")
            && (isNTLM(authenticateHeaders) || negociate)) {
          newRealm =
              ntlmChallenge(
                  authenticateHeaders, request, proxyServer, request.getHeaders(), realm, future);
          // SPNEGO KERBEROS
        } else if (negociate) {
          newRealm =
              kerberosChallenge(
                  authenticateHeaders, request, proxyServer, request.getHeaders(), realm, future);
          if (newRealm == null) {
            return true;
          }
        } else {
          newRealm =
              new Realm.RealmBuilder()
                  .clone(realm)
                  .setScheme(realm.getAuthScheme())
                  .setUri(request.getURI().getPath())
                  .setMethodName(request.getMethod())
                  .setUsePreemptiveAuth(true)
                  .parseWWWAuthenticateHeader(authenticateHeaders.get(0))
                  .build();
        }

        Realm nr =
            new Realm.RealmBuilder()
                .clone(newRealm)
                .setUri(URI.create(request.getUrl()).getPath())
                .build();
        final Request nextRequest =
            new RequestBuilder(future.getRequest())
                .setHeaders(request.getHeaders())
                .setRealm(nr)
                .build();

        LOGGER.debug("Sending authentication to {}", request.getUrl());
        Callback callback =
            new Callback(future) {
              public void call() throws Exception {
                channels.drainChannel(channel, future);
                requestSender.sendNextRequest(nextRequest, future);
              }
            };

        if (future.isKeepAlive() && HttpHeaders.isTransferEncodingChunked(response)) {
          // We must make sure there is no bytes left
          // before executing the next request.
          Channels.setDefaultAttribute(channel, callback);
        } else {
          callback.call();
        }

        return true;
      }
    }

    return false;
  }
 private static List<String> getProxyAuthorizationHeader(Request request) {
   return request.getHeaders().get(PROXY_AUTHORIZATION_HEADER);
 }
  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;
  }