Repository: netty/netty
Issue: Add validation check about websocket path

### Expected behavior

If I add WebSocketServerProtocolHandler like below,
```java
private final String WEBSOCKET_PATH = "/websocket";

// ~~

final ChannelPipeline p = ch.pipeline();

p.addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PATH, true));
```
I expected :
 `/websocket/a` : return true
 `/websocketabc` : return false
 `/websocket?name=doyuni` : return true

### Actual behavior

But it return true on all above case.

I don't want this handler to pass `/websocketabc` through.

### Steps to reproduce

Just modify one method like below code. 

### Minimal yet complete reproducer code (or URL to code)
- WebSocketServerProtocolHandshakeHandler
```java
private boolean isNotWebSocketPath(FullHttpRequest req) {
    String websocketPath = serverConfig.websocketPath();
    char nextStartUri = '/';
    if (req.uri().length() > websocketPath.length()) nextStartUri = req.uri().charAt(websocketPath.length());  
    return serverConfig.checkStartsWith() ? !(req.uri().startsWith(websocketPath) && (nextStartUri == '/' || nextStartUri == '?')) 
                                          : !req.uri().equals(websocketPath);
}
```

### Netty version

- 4.1.52.Final

### JVM version (e.g. `java -version`)
```
java version "11.0.5" 2019-10-15 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.5+10-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.5+10-LTS, mixed mode)
```
### OS version (e.g. `uname -a`)

- Mac OS Catalina v. 10.15.6

Issue comments:
@Doyuni please open a PR with the fix and a testcase- 
> @Doyuni please open a PR with the fix and a testcase-

Okay I got it.

Pull request review comments:
`req.uri()` could be moved to a variable.
All this should be done only in case of `serverConfig.checkStartsWith()`. In all other cases, we don't need to perform those checks.
Okay, thank you for reviewing.
So..what should I do? That logic could be moved to new method?
Probably own method makes sense as it became big enough.

Also, I think it would make sense to rename **isNotWebSocketPath** to **isWebSocketPath**. It's a generic Java way. When **NO** is required you can use **!isWebSocketPath()**.

Thank you for your kind explanation.

I'll change it based on your reference.

Patch that resolved the issue:
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java
index ec9b4ff1049..f3f53327563 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandshakeHandler.java
@@ -61,7 +61,7 @@ public void handlerAdded(ChannelHandlerContext ctx) {
     @Override
     public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
         final FullHttpRequest req = (FullHttpRequest) msg;
-        if (isNotWebSocketPath(req)) {
+        if (!isWebSocketPath(req)) {
             ctx.fireChannelRead(msg);
             return;
         }
@@ -113,9 +113,21 @@ public void operationComplete(ChannelFuture future) {
         }
     }
 
-    private boolean isNotWebSocketPath(FullHttpRequest req) {
+    private boolean isWebSocketPath(FullHttpRequest req) {
         String websocketPath = serverConfig.websocketPath();
-        return serverConfig.checkStartsWith() ? !req.uri().startsWith(websocketPath) : !req.uri().equals(websocketPath);
+        String uri = req.uri();
+        boolean checkStartUri = uri.startsWith(websocketPath);
+        boolean checkNextUri = checkNextUri(uri, websocketPath);
+        return serverConfig.checkStartsWith() ? (checkStartUri && checkNextUri) : uri.equals(websocketPath);
+    }
+
+    private boolean checkNextUri(String uri, String websocketPath) {
+        int len = websocketPath.length();
+        if (uri.length() > len) {
+            char nextUri = uri.charAt(len);
+            return nextUri == '/' || nextUri == '?';
+        }
+        return true;
     }
 
     private static void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {


Test patch:
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java
index 69cda51f0b4..1ca29b0a5e6 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java
@@ -22,17 +22,18 @@
 import io.netty.channel.ChannelOutboundHandlerAdapter;
 import io.netty.channel.ChannelPromise;
 import io.netty.channel.embedded.EmbeddedChannel;
-import io.netty.handler.codec.http.DefaultFullHttpRequest;
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.FullHttpResponse;
+
 import io.netty.handler.codec.http.HttpClientCodec;
 import io.netty.handler.codec.http.HttpHeaderValues;
-import io.netty.handler.codec.http.HttpMethod;
-import io.netty.handler.codec.http.HttpObjectAggregator;
-import io.netty.handler.codec.http.HttpRequest;
 import io.netty.handler.codec.http.HttpRequestDecoder;
 import io.netty.handler.codec.http.HttpResponseEncoder;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpObjectAggregator;
 import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.HttpHeaderNames;
 import io.netty.util.CharsetUtil;
 import io.netty.util.ReferenceCountUtil;
 import org.junit.Before;
@@ -194,6 +195,61 @@ public void testHandleTextFrame() {
         assertFalse(ch.finish());
     }
 
+    @Test
+    public void testCheckValidWebSocketPath() {
+        HttpRequest httpRequest = new WebSocketRequestBuilder().httpVersion(HTTP_1_1)
+                .method(HttpMethod.GET)
+                .uri("/test")
+                .key(HttpHeaderNames.SEC_WEBSOCKET_KEY)
+                .connection("Upgrade")
+                .upgrade(HttpHeaderValues.WEBSOCKET)
+                .version13()
+                .build();
+
+        WebSocketServerProtocolConfig config = WebSocketServerProtocolConfig.newBuilder()
+                .websocketPath("/test")
+                .checkStartsWith(true)
+                .build();
+
+        EmbeddedChannel ch = new EmbeddedChannel(
+                new WebSocketServerProtocolHandler(config),
+                new HttpRequestDecoder(),
+                new HttpResponseEncoder(),
+                new MockOutboundHandler());
+        ch.writeInbound(httpRequest);
+
+        FullHttpResponse response = responses.remove();
+        assertEquals(SWITCHING_PROTOCOLS, response.status());
+        response.release();
+    }
+
+    @Test
+    public void testCheckInvalidWebSocketPath() {
+        HttpRequest httpRequest = new WebSocketRequestBuilder().httpVersion(HTTP_1_1)
+                .method(HttpMethod.GET)
+                .uri("/testabc")
+                .key(HttpHeaderNames.SEC_WEBSOCKET_KEY)
+                .connection("Upgrade")
+                .upgrade(HttpHeaderValues.WEBSOCKET)
+                .version13()
+                .build();
+
+        WebSocketServerProtocolConfig config = WebSocketServerProtocolConfig.newBuilder()
+                .websocketPath("/test")
+                .checkStartsWith(true)
+                .build();
+
+        EmbeddedChannel ch = new EmbeddedChannel(
+                new WebSocketServerProtocolHandler(config),
+                new HttpRequestDecoder(),
+                new HttpResponseEncoder(),
+                new MockOutboundHandler());
+        ch.writeInbound(httpRequest);
+
+        ChannelHandlerContext handshakerCtx = ch.pipeline().context(WebSocketServerProtocolHandshakeHandler.class);
+        assertNull(WebSocketServerProtocolHandler.getHandshaker(handshakerCtx.channel()));
+    }
+
     @Test
     public void testExplicitCloseFrameSentWhenServerChannelClosed() throws Exception {
         WebSocketCloseStatus closeStatus = WebSocketCloseStatus.ENDPOINT_UNAVAILABLE;