// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC 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.openqa.selenium.netty.server;

import static io.netty.handler.codec.http.HttpMethod.HEAD;
import static org.openqa.selenium.remote.http.Contents.memoize;

import com.google.common.io.ByteStreams;

import org.openqa.selenium.internal.Debug;
import org.openqa.selenium.remote.http.HttpMethod;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
import org.openqa.selenium.remote.tracing.AttributeKey;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.util.ReferenceCountUtil;

import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.UncheckedIOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

class RequestConverter extends SimpleChannelInboundHandler<HttpObject> {

  private static final Logger LOG = Logger.getLogger(RequestConverter.class.getName());
  private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
  private volatile PipedOutputStream out;

  @Override
  protected void channelRead0(
    ChannelHandlerContext ctx,
    HttpObject msg) throws Exception {
    LOG.log(Debug.getDebugLogLevel(), "Incoming message: " + msg);

    if (msg instanceof io.netty.handler.codec.http.HttpRequest) {
      LOG.log(Debug.getDebugLogLevel(), "Start of http request: " + msg);

      io.netty.handler.codec.http.HttpRequest nettyRequest =
        (io.netty.handler.codec.http.HttpRequest) msg;

      if (HttpUtil.is100ContinueExpected(nettyRequest)) {
        ctx.write(new HttpResponse().setStatus(100));
        return;
      }

      if (nettyRequest.headers().contains("Sec-WebSocket-Version") &&
        "upgrade".equals(nettyRequest.headers().get("Connection"))) {
        // Pass this on to later in the pipeline.
        ReferenceCountUtil.retain(msg);
        ctx.fireChannelRead(msg);
        return;
      }

      HttpRequest req = createRequest(ctx, nettyRequest);
      if (req == null) {
        return;
      }

      req.setAttribute(AttributeKey.HTTP_SCHEME.getKey(),
        nettyRequest.protocolVersion().protocolName());
      req.setAttribute(AttributeKey.HTTP_FLAVOR.getKey(),
        nettyRequest.protocolVersion().majorVersion());

      out = new PipedOutputStream();
      InputStream in = new PipedInputStream(out);

      req.setContent(memoize(() -> in));
      ctx.fireChannelRead(req);
    }

    if (msg instanceof HttpContent) {
      ByteBuf buf = ((HttpContent) msg).content().retain();
      EXECUTOR.submit(() -> {
        try (InputStream inputStream = new ByteBufInputStream(buf)) {
          ByteStreams.copy(inputStream, out);
        } catch (IOException e) {
          throw new UncheckedIOException(e);
        } finally {
          buf.release();
        }
      });
    }

    if (msg instanceof LastHttpContent) {
      LOG.log(Debug.getDebugLogLevel(), "Closing input pipe.");
      EXECUTOR.submit(() -> {
        try {
          out.close();
        } catch (IOException e) {
          throw new UncheckedIOException(e);
        }
      });
    }
  }

  private HttpRequest createRequest(
    ChannelHandlerContext ctx,
    io.netty.handler.codec.http.HttpRequest nettyRequest) {

    // Attempt to map the netty method
    HttpMethod method;
    if (nettyRequest.method().equals(HEAD)) {
      method = HttpMethod.GET;
    } else {
      try {
        method = HttpMethod.valueOf(nettyRequest.method().name());
      } catch (IllegalArgumentException e) {
        ctx.writeAndFlush(
          new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.METHOD_NOT_ALLOWED));
        return null;
      }
    }

    // Attempt to decode parameters
    try {
      QueryStringDecoder decoder = new QueryStringDecoder(nettyRequest.uri());

      HttpRequest req = new HttpRequest(
        method,
        decoder.path());

      decoder.parameters()
        .forEach((key, values) -> values.forEach(value -> req.addQueryParameter(key, value)));

      nettyRequest.headers().entries().stream()
        .filter(entry -> entry.getKey() != null)
        .forEach(entry -> req.addHeader(entry.getKey(), entry.getValue()));
      return req;
    } catch (Exception e) {
      ctx.writeAndFlush(
        new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
      LOG.log(Debug.getDebugLogLevel(), "Not possible to decode parameters.", e);
      return null;
    }
  }
}

@@ -893,20 +893,260 @@
 pMethod.
-HEAD
+DELETE;%0Aimport static io.netty.handler.codec.http.HttpMethod.GET;%0Aimport static io.netty.handler.codec.http.HttpMethod.HEAD;%0Aimport static io.netty.handler.codec.http.HttpMethod.OPTIONS;%0Aimport static io.netty.handler.codec.http.HttpMethod.POST
 ;%0Aimport
@@ -2281,32 +2281,80 @@
 kedIOException;%0A
+import java.util.Arrays;%0Aimport java.util.List;%0A
 import java.util
@@ -2745,16 +2745,150 @@
 eam out;
+%0A  private static final List%3Cio.netty.handler.codec.http.HttpMethod%3E supportedMethods =%0A    Arrays.asList(DELETE, GET, POST, OPTIONS);
 %0A%0A  @Ove
@@ -5207,16 +5207,70 @@
   %7D else
+ if (supportedMethods.contains(nettyRequest.method()))
  %7B%0A     
@@ -5533,24 +5533,24 @@
 eturn null;%0A
-
       %7D%0A    
@@ -5546,24 +5546,180 @@
      %7D%0A    %7D
+ else %7B%0A      ctx.writeAndFlush(%0A        new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.METHOD_NOT_ALLOWED));%0A      return null;%0A    %7D
 %0A%0A    // Att

Add supported methods check