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


##########
processing/src/test/java/org/apache/druid/java/util/http/client/FriendlyServersTest.java:
##########
@@ -384,11 +389,120 @@
                 StatusResponseHandler.getInstance()
             ).get().getStatus();
 
-        Assert.assertEquals(200, status.getCode());
+        Assert.assertEquals(200, status.code());
       }
     }
     finally {
       lifecycle.stop();
     }
   }
+
+  /**
+   * Regression test for the case where the same {@link Request} (or a copy of 
it) is sent more
+   * than once. Netty 4's HttpObjectEncoder advances the content's reader 
index and releases
+   * {@link io.netty.handler.codec.http.FullHttpRequest} after encoding; if 
{@link NettyHttpClient}
+   * handed the Request's stored {@link io.netty.buffer.ByteBuf} directly to 
the encoder, the
+   * second send would either drain to zero bytes or hit an {@link 
io.netty.util.IllegalReferenceCountException}.
+   *
+   * KerberosHttpClient hits this path: on a 401 it calls {@code 
Request.copy()} (which deep-copies
+   * the content) and re-sends. This test exercises the same shape.
+   */
+  @Test
+  public void testRequestCanBeSentMultipleTimes() throws Exception
+  {
+    final ExecutorService exec = Executors.newCachedThreadPool();
+    final ServerSocket serverSocket = new ServerSocket(0);
+    final BlockingQueue<String> receivedBodies = new LinkedBlockingQueue<>();
+    // Keep-alive HTTP/1.1 loop: each accepted connection serves consecutive 
requests until the
+    // peer disconnects. Needed because the client's channel pool reuses 
connections, and a
+    // single-shot server would cause a write-to-closed-channel race on the 
second send.
+    exec.submit(
+        () -> {
+          while (!Thread.currentThread().isInterrupted()) {
+            final Socket clientSocket;
+            try {
+              clientSocket = serverSocket.accept();
+            }
+            catch (IOException e) {
+              return;
+            }
+            exec.submit(() -> {
+              try (
+                  Socket s = clientSocket;
+                  BufferedReader in = new BufferedReader(
+                      new InputStreamReader(s.getInputStream(), 
StandardCharsets.UTF_8)
+                  );
+                  OutputStream out = s.getOutputStream()
+              ) {
+                while (true) {
+                  String requestLine = in.readLine();
+                  if (requestLine == null) {
+                    return;
+                  }
+                  int contentLength = 0;
+                  String header;
+                  while (!(header = 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/11304)



##########
processing/src/test/java/org/apache/druid/java/util/http/client/FriendlyServersTest.java:
##########
@@ -384,11 +389,120 @@
                 StatusResponseHandler.getInstance()
             ).get().getStatus();
 
-        Assert.assertEquals(200, status.getCode());
+        Assert.assertEquals(200, status.code());
       }
     }
     finally {
       lifecycle.stop();
     }
   }
+
+  /**
+   * Regression test for the case where the same {@link Request} (or a copy of 
it) is sent more
+   * than once. Netty 4's HttpObjectEncoder advances the content's reader 
index and releases
+   * {@link io.netty.handler.codec.http.FullHttpRequest} after encoding; if 
{@link NettyHttpClient}
+   * handed the Request's stored {@link io.netty.buffer.ByteBuf} directly to 
the encoder, the
+   * second send would either drain to zero bytes or hit an {@link 
io.netty.util.IllegalReferenceCountException}.
+   *
+   * KerberosHttpClient hits this path: on a 401 it calls {@code 
Request.copy()} (which deep-copies
+   * the content) and re-sends. This test exercises the same shape.
+   */
+  @Test
+  public void testRequestCanBeSentMultipleTimes() throws Exception
+  {
+    final ExecutorService exec = Executors.newCachedThreadPool();
+    final ServerSocket serverSocket = new ServerSocket(0);
+    final BlockingQueue<String> receivedBodies = new LinkedBlockingQueue<>();
+    // Keep-alive HTTP/1.1 loop: each accepted connection serves consecutive 
requests until the
+    // peer disconnects. Needed because the client's channel pool reuses 
connections, and a
+    // single-shot server would cause a write-to-closed-channel race on the 
second send.
+    exec.submit(
+        () -> {
+          while (!Thread.currentThread().isInterrupted()) {
+            final Socket clientSocket;
+            try {
+              clientSocket = serverSocket.accept();
+            }
+            catch (IOException e) {
+              return;
+            }
+            exec.submit(() -> {
+              try (
+                  Socket s = clientSocket;
+                  BufferedReader in = new BufferedReader(
+                      new InputStreamReader(s.getInputStream(), 
StandardCharsets.UTF_8)
+                  );
+                  OutputStream out = s.getOutputStream()
+              ) {
+                while (true) {
+                  String requestLine = in.readLine();
+                  if (requestLine == null) {
+                    return;
+                  }
+                  int contentLength = 0;
+                  String header;
+                  while (!(header = in.readLine()).equals("")) {
+                    if 
(header.toLowerCase(Locale.ROOT).startsWith("content-length:")) {
+                      contentLength = 
Integer.parseInt(header.substring("content-length:".length()).trim());
+                    }
+                  }
+                  char[] body = new char[contentLength];
+                  int read = 0;
+                  while (read < contentLength) {
+                    int n = in.read(body, read, contentLength - read);
+                    if (n < 0) {
+                      return;
+                    }
+                    read += n;
+                  }
+                  receivedBodies.put(new String(body, 0, read));
+                  out.write("HTTP/1.1 200 OK\r\nContent-Length: 
2\r\n\r\nok".getBytes(StandardCharsets.UTF_8));
+                  out.flush();
+                }
+              }
+              catch (Exception ignored) {
+                // suppress
+              }
+            });
+          }
+        }
+    );
+
+    final Lifecycle lifecycle = new Lifecycle();
+    try {
+      final HttpClient client = 
HttpClientInit.createClient(HttpClientConfig.builder().build(), lifecycle);
+      final URL url = 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/11303)



-- 
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