github-advanced-security[bot] commented on code in PR #20151:
URL: https://github.com/apache/druid/pull/20151#discussion_r3940752088


##########
processing/src/test/java/org/apache/druid/java/util/http/client/NettyHttpClientTest.java:
##########
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.java.util.http.client;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.java.util.common.lifecycle.Lifecycle;
+import org.apache.druid.java.util.http.client.response.ClientResponse;
+import org.apache.druid.java.util.http.client.response.HttpResponseHandler;
+import org.jboss.netty.handler.codec.http.HttpChunk;
+import org.jboss.netty.handler.codec.http.HttpMethod;
+import org.jboss.netty.handler.codec.http.HttpResponse;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link NettyHttpClient} exercising real socket I/O.
+ */
+public class NettyHttpClientTest
+{
+  /**
+   * A response handler whose {@link #handleResponse} throws, simulating a 
handler that rejects the response (for
+   * example because of an unexpected status code or content type) before any 
content has been processed.
+   */
+  private static class ThrowingResponseHandler implements 
HttpResponseHandler<Object, Object>
+  {
+    private final RuntimeException toThrow;
+
+    ThrowingResponseHandler(RuntimeException toThrow)
+    {
+      this.toThrow = toThrow;
+    }
+
+    @Override
+    public ClientResponse<Object> handleResponse(HttpResponse response, 
TrafficCop trafficCop)
+    {
+      throw toThrow;
+    }
+
+    @Override
+    public ClientResponse<Object> handleChunk(ClientResponse<Object> 
clientResponse, HttpChunk chunk, long chunkNum)
+    {
+      return clientResponse;
+    }
+
+    @Override
+    public ClientResponse<Object> done(ClientResponse<Object> clientResponse)
+    {
+      return ClientResponse.finished(clientResponse.getObj());
+    }
+
+    @Override
+    public void exceptionCaught(ClientResponse<Object> clientResponse, 
Throwable e)
+    {
+      // Nothing to do.
+    }
+  }
+
+  /**
+   * Regression test: an exception thrown from {@link 
HttpResponseHandler#handleResponse} must fail the future
+   * returned by {@link HttpClient#go}, not resolve it to {@code null}. 
Previously, {@link NettyHttpClient} would
+   * call {@code retVal.set(null)} in its catch block before rethrowing, which 
meant the thrown exception was
+   * discarded and callers observed a successful null result instead of a 
failure.
+   */
+  @Test
+  public void testHandleResponseExceptionFailsFuture() throws Exception
+  {
+    final ExecutorService exec = Executors.newSingleThreadExecutor();
+    final ServerSocket serverSocket = new ServerSocket(0);
+    serveRawResponse(exec, serverSocket, "HTTP/1.1 200 OK\r\nContent-Length: 
2\r\n\r\n{}");
+
+    final Lifecycle lifecycle = new Lifecycle();
+    try {
+      final HttpClientConfig config = HttpClientConfig.builder().build();
+      final HttpClient client = HttpClientInit.createClient(config, lifecycle);
+
+      final RuntimeException expected = new RuntimeException("boom from 
handleResponse");
+      final ListenableFuture<Object> future = client.go(
+          new Request(
+              HttpMethod.GET,
+              new URL(StringUtils.format("http://localhost:%d/";, 
serverSocket.getLocalPort()))

Review Comment:
   ## CodeQL / Deprecated method or constructor invocation
   
   Invoking [URL.URL](1) should be avoided because it has been deprecated.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11937)



##########
processing/src/test/java/org/apache/druid/java/util/http/client/NettyHttpClientTest.java:
##########
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.java.util.http.client;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.java.util.common.lifecycle.Lifecycle;
+import org.apache.druid.java.util.http.client.response.ClientResponse;
+import org.apache.druid.java.util.http.client.response.HttpResponseHandler;
+import org.jboss.netty.handler.codec.http.HttpChunk;
+import org.jboss.netty.handler.codec.http.HttpMethod;
+import org.jboss.netty.handler.codec.http.HttpResponse;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link NettyHttpClient} exercising real socket I/O.
+ */
+public class NettyHttpClientTest
+{
+  /**
+   * A response handler whose {@link #handleResponse} throws, simulating a 
handler that rejects the response (for
+   * example because of an unexpected status code or content type) before any 
content has been processed.
+   */
+  private static class ThrowingResponseHandler implements 
HttpResponseHandler<Object, Object>
+  {
+    private final RuntimeException toThrow;
+
+    ThrowingResponseHandler(RuntimeException toThrow)
+    {
+      this.toThrow = toThrow;
+    }
+
+    @Override
+    public ClientResponse<Object> handleResponse(HttpResponse response, 
TrafficCop trafficCop)
+    {
+      throw toThrow;
+    }
+
+    @Override
+    public ClientResponse<Object> handleChunk(ClientResponse<Object> 
clientResponse, HttpChunk chunk, long chunkNum)
+    {
+      return clientResponse;
+    }
+
+    @Override
+    public ClientResponse<Object> done(ClientResponse<Object> clientResponse)
+    {
+      return ClientResponse.finished(clientResponse.getObj());
+    }
+
+    @Override
+    public void exceptionCaught(ClientResponse<Object> clientResponse, 
Throwable e)
+    {
+      // Nothing to do.
+    }
+  }
+
+  /**
+   * Regression test: an exception thrown from {@link 
HttpResponseHandler#handleResponse} must fail the future
+   * returned by {@link HttpClient#go}, not resolve it to {@code null}. 
Previously, {@link NettyHttpClient} would
+   * call {@code retVal.set(null)} in its catch block before rethrowing, which 
meant the thrown exception was
+   * discarded and callers observed a successful null result instead of a 
failure.
+   */
+  @Test
+  public void testHandleResponseExceptionFailsFuture() throws Exception
+  {
+    final ExecutorService exec = Executors.newSingleThreadExecutor();
+    final ServerSocket serverSocket = new ServerSocket(0);
+    serveRawResponse(exec, serverSocket, "HTTP/1.1 200 OK\r\nContent-Length: 
2\r\n\r\n{}");
+
+    final Lifecycle lifecycle = new Lifecycle();
+    try {
+      final HttpClientConfig config = HttpClientConfig.builder().build();
+      final HttpClient client = HttpClientInit.createClient(config, lifecycle);
+
+      final RuntimeException expected = new RuntimeException("boom from 
handleResponse");
+      final ListenableFuture<Object> future = client.go(
+          new Request(
+              HttpMethod.GET,
+              new URL(StringUtils.format("http://localhost:%d/";, 
serverSocket.getLocalPort()))
+          ),
+          new ThrowingResponseHandler(expected)
+      );
+
+      final ExecutionException e = 
Assertions.assertThrows(ExecutionException.class, future::get);
+      Assertions.assertSame(expected, e.getCause(), "the exception thrown by 
handleResponse must not be lost");
+    }
+    finally {
+      exec.shutdownNow();
+      serverSocket.close();
+      lifecycle.stop();
+    }
+  }
+
+  /**
+   * A response handler that (like DirectDruidClient) completes the future 
from {@link #handleResponse}, then
+   * throws from {@link #handleChunk} on a later chunk, and records whatever 
{@link #exceptionCaught} is eventually
+   * handed.
+   */
+  private static class ThrowingChunkHandler implements 
HttpResponseHandler<Object, Object>
+  {
+    private final RuntimeException toThrow;
+    private final SettableFuture<Throwable> caught = SettableFuture.create();
+
+    ThrowingChunkHandler(RuntimeException toThrow)
+    {
+      this.toThrow = toThrow;
+    }
+
+    @Override
+    public ClientResponse<Object> handleResponse(HttpResponse response, 
TrafficCop trafficCop)
+    {
+      return ClientResponse.finished("initial");
+    }
+
+    @Override
+    public ClientResponse<Object> handleChunk(ClientResponse<Object> 
clientResponse, HttpChunk chunk, long chunkNum)
+    {
+      if (chunkNum >= 2) {
+        throw toThrow;
+      }
+      return clientResponse;
+    }
+
+    @Override
+    public ClientResponse<Object> done(ClientResponse<Object> clientResponse)
+    {
+      return ClientResponse.finished(clientResponse.getObj());
+    }
+
+    @Override
+    public void exceptionCaught(ClientResponse<Object> clientResponse, 
Throwable e)
+    {
+      caught.set(e);
+    }
+  }
+
+  /**
+   * Regression test: when the future has already been completed by {@link 
HttpResponseHandler#handleResponse} (as
+   * DirectDruidClient does for chunked responses), an exception thrown by 
{@link HttpResponseHandler#handleChunk}
+   * on a later chunk must be delivered to {@link 
HttpResponseHandler#exceptionCaught} as itself. Previously the
+   * catch block only closed the channel, so the handler instead saw the 
generic "Channel disconnected"
+   * {@link org.jboss.netty.channel.ChannelException} raised by the resulting 
disconnect, and the real cause was lost.
+   */
+  @Test
+  public void testHandleChunkExceptionReachesExceptionCaught() throws Exception
+  {
+    final ExecutorService exec = Executors.newSingleThreadExecutor();
+    final ServerSocket serverSocket = new ServerSocket(0);
+    serveRawResponse(
+        exec,
+        serverSocket,
+        "HTTP/1.1 200 OK\r\nTransfer-Encoding: 
chunked\r\n\r\n2\r\n{}\r\n6\r\n<html>\r\n0\r\n\r\n"
+    );
+
+    final Lifecycle lifecycle = new Lifecycle();
+    try {
+      final HttpClientConfig config = HttpClientConfig.builder().build();
+      final HttpClient client = HttpClientInit.createClient(config, lifecycle);
+
+      final RuntimeException expected = new RuntimeException("boom from 
handleChunk");
+      final ThrowingChunkHandler handler = new ThrowingChunkHandler(expected);
+      final ListenableFuture<Object> future = client.go(
+          new Request(
+              HttpMethod.GET,
+              new URL(StringUtils.format("http://localhost:%d/";, 
serverSocket.getLocalPort()))

Review Comment:
   ## CodeQL / Deprecated method or constructor invocation
   
   Invoking [URL.URL](1) should be avoided because it has been deprecated.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11938)



##########
processing/src/test/java/org/apache/druid/java/util/http/client/NettyHttpClientTest.java:
##########
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.java.util.http.client;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.java.util.common.lifecycle.Lifecycle;
+import org.apache.druid.java.util.http.client.response.ClientResponse;
+import org.apache.druid.java.util.http.client.response.HttpResponseHandler;
+import org.jboss.netty.handler.codec.http.HttpChunk;
+import org.jboss.netty.handler.codec.http.HttpMethod;
+import org.jboss.netty.handler.codec.http.HttpResponse;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link NettyHttpClient} exercising real socket I/O.
+ */
+public class NettyHttpClientTest
+{
+  /**
+   * A response handler whose {@link #handleResponse} throws, simulating a 
handler that rejects the response (for
+   * example because of an unexpected status code or content type) before any 
content has been processed.
+   */
+  private static class ThrowingResponseHandler implements 
HttpResponseHandler<Object, Object>
+  {
+    private final RuntimeException toThrow;
+
+    ThrowingResponseHandler(RuntimeException toThrow)
+    {
+      this.toThrow = toThrow;
+    }
+
+    @Override
+    public ClientResponse<Object> handleResponse(HttpResponse response, 
TrafficCop trafficCop)
+    {
+      throw toThrow;
+    }
+
+    @Override
+    public ClientResponse<Object> handleChunk(ClientResponse<Object> 
clientResponse, HttpChunk chunk, long chunkNum)
+    {
+      return clientResponse;
+    }
+
+    @Override
+    public ClientResponse<Object> done(ClientResponse<Object> clientResponse)
+    {
+      return ClientResponse.finished(clientResponse.getObj());
+    }
+
+    @Override
+    public void exceptionCaught(ClientResponse<Object> clientResponse, 
Throwable e)
+    {
+      // Nothing to do.
+    }
+  }
+
+  /**
+   * Regression test: an exception thrown from {@link 
HttpResponseHandler#handleResponse} must fail the future
+   * returned by {@link HttpClient#go}, not resolve it to {@code null}. 
Previously, {@link NettyHttpClient} would
+   * call {@code retVal.set(null)} in its catch block before rethrowing, which 
meant the thrown exception was
+   * discarded and callers observed a successful null result instead of a 
failure.
+   */
+  @Test
+  public void testHandleResponseExceptionFailsFuture() throws Exception
+  {
+    final ExecutorService exec = Executors.newSingleThreadExecutor();
+    final ServerSocket serverSocket = new ServerSocket(0);
+    serveRawResponse(exec, serverSocket, "HTTP/1.1 200 OK\r\nContent-Length: 
2\r\n\r\n{}");
+
+    final Lifecycle lifecycle = new Lifecycle();
+    try {
+      final HttpClientConfig config = HttpClientConfig.builder().build();
+      final HttpClient client = HttpClientInit.createClient(config, lifecycle);
+
+      final RuntimeException expected = new RuntimeException("boom from 
handleResponse");
+      final ListenableFuture<Object> future = client.go(
+          new Request(
+              HttpMethod.GET,
+              new URL(StringUtils.format("http://localhost:%d/";, 
serverSocket.getLocalPort()))
+          ),
+          new ThrowingResponseHandler(expected)
+      );
+
+      final ExecutionException e = 
Assertions.assertThrows(ExecutionException.class, future::get);
+      Assertions.assertSame(expected, e.getCause(), "the exception thrown by 
handleResponse must not be lost");
+    }
+    finally {
+      exec.shutdownNow();
+      serverSocket.close();
+      lifecycle.stop();
+    }
+  }
+
+  /**
+   * A response handler that (like DirectDruidClient) completes the future 
from {@link #handleResponse}, then
+   * throws from {@link #handleChunk} on a later chunk, and records whatever 
{@link #exceptionCaught} is eventually
+   * handed.
+   */
+  private static class ThrowingChunkHandler implements 
HttpResponseHandler<Object, Object>
+  {
+    private final RuntimeException toThrow;
+    private final SettableFuture<Throwable> caught = SettableFuture.create();
+
+    ThrowingChunkHandler(RuntimeException toThrow)
+    {
+      this.toThrow = toThrow;
+    }
+
+    @Override
+    public ClientResponse<Object> handleResponse(HttpResponse response, 
TrafficCop trafficCop)
+    {
+      return ClientResponse.finished("initial");
+    }
+
+    @Override
+    public ClientResponse<Object> handleChunk(ClientResponse<Object> 
clientResponse, HttpChunk chunk, long chunkNum)
+    {
+      if (chunkNum >= 2) {
+        throw toThrow;
+      }
+      return clientResponse;
+    }
+
+    @Override
+    public ClientResponse<Object> done(ClientResponse<Object> clientResponse)
+    {
+      return ClientResponse.finished(clientResponse.getObj());
+    }
+
+    @Override
+    public void exceptionCaught(ClientResponse<Object> clientResponse, 
Throwable e)
+    {
+      caught.set(e);
+    }
+  }
+
+  /**
+   * Regression test: when the future has already been completed by {@link 
HttpResponseHandler#handleResponse} (as
+   * DirectDruidClient does for chunked responses), an exception thrown by 
{@link HttpResponseHandler#handleChunk}
+   * on a later chunk must be delivered to {@link 
HttpResponseHandler#exceptionCaught} as itself. Previously the
+   * catch block only closed the channel, so the handler instead saw the 
generic "Channel disconnected"
+   * {@link org.jboss.netty.channel.ChannelException} raised by the resulting 
disconnect, and the real cause was lost.
+   */
+  @Test
+  public void testHandleChunkExceptionReachesExceptionCaught() throws Exception
+  {
+    final ExecutorService exec = Executors.newSingleThreadExecutor();
+    final ServerSocket serverSocket = new ServerSocket(0);
+    serveRawResponse(
+        exec,
+        serverSocket,
+        "HTTP/1.1 200 OK\r\nTransfer-Encoding: 
chunked\r\n\r\n2\r\n{}\r\n6\r\n<html>\r\n0\r\n\r\n"
+    );
+
+    final Lifecycle lifecycle = new Lifecycle();
+    try {
+      final HttpClientConfig config = HttpClientConfig.builder().build();
+      final HttpClient client = HttpClientInit.createClient(config, lifecycle);
+
+      final RuntimeException expected = new RuntimeException("boom from 
handleChunk");
+      final ThrowingChunkHandler handler = new ThrowingChunkHandler(expected);
+      final ListenableFuture<Object> future = client.go(
+          new Request(
+              HttpMethod.GET,
+              new URL(StringUtils.format("http://localhost:%d/";, 
serverSocket.getLocalPort()))
+          ),
+          handler
+      );
+
+      Assertions.assertEquals("initial", future.get(10, TimeUnit.SECONDS));
+      Assertions.assertSame(
+          expected,
+          handler.caught.get(10, TimeUnit.SECONDS),
+          "the exception thrown by handleChunk must reach exceptionCaught, not 
a generic channel-disconnected error"
+      );
+    }
+    finally {
+      exec.shutdownNow();
+      serverSocket.close();
+      lifecycle.stop();
+    }
+  }
+
+  /**
+   * Accepts connections on {@code serverSocket} until interrupted; for each, 
reads the request headers and writes
+   * {@code rawResponse} verbatim.
+   */
+  private static void serveRawResponse(ExecutorService exec, ServerSocket 
serverSocket, String rawResponse)
+  {
+    exec.submit(
+        new Runnable()
+        {
+          @Override
+          public void run()
+          {
+            while (!Thread.currentThread().isInterrupted()) {
+              try (
+                  Socket clientSocket = serverSocket.accept();
+                  BufferedReader in = new BufferedReader(
+                      new InputStreamReader(clientSocket.getInputStream(), 
StandardCharsets.UTF_8)
+                  );
+                  OutputStream out = clientSocket.getOutputStream()
+              ) {
+                while (!in.readLine().equals("")) {

Review Comment:
   ## CodeQL / Inefficient empty string test
   
   Inefficient comparison to empty string, check for zero length instead.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11936)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to