diff --git a/core/src/main/java/io/micronaut/core/util/StringUtils.java b/core/src/main/java/io/micronaut/core/util/StringUtils.java index 78abd3b5468..ec1e77e08c9 100644 --- a/core/src/main/java/io/micronaut/core/util/StringUtils.java +++ b/core/src/main/java/io/micronaut/core/util/StringUtils.java @@ -366,18 +366,35 @@ public static String convertDotToUnderscore(String dottedProperty, boolean upper * @return A combined uri string */ public static String prependUri(String baseUri, String uri) { - if (!uri.startsWith("/") && !uri.startsWith("?")) { - uri = "/" + uri; + StringBuilder builder = new StringBuilder(baseUri); + if (!uri.isEmpty() && (uri.length() != 1 || uri.charAt(0) != '/')) { + if (!uri.startsWith("/") && !uri.startsWith("?")) { + builder.append('/'); + } + builder.append(uri); + } + if (builder.isEmpty()) { + return ""; } - if (uri.length() == 1 && uri.charAt(0) == '/') { - uri = ""; + + int i = 0; + if (builder.charAt(0) != '/' && builder.indexOf("://") != -1) { + // skip until after scheme + while (i < builder.length() && builder.charAt(i) != ':') { + i++; + } + i += 2; } - uri = baseUri + uri; - if (uri.startsWith("/")) { - return uri.replaceAll("/{2,}", "/"); - } else { - return uri.replaceAll("(?<=[^:])/{2,}", "/"); + // replace double slashes + for (; i < builder.length() - 1; i++) { + if (builder.charAt(i) == '/' && builder.charAt(i + 1) == '/') { + builder.deleteCharAt(i); + i--; + } else if (builder.charAt(i) == '?') { + break; + } } + return builder.toString(); } /** diff --git a/http-client/src/main/java/io/micronaut/http/client/netty/DefaultHttpClient.java b/http-client/src/main/java/io/micronaut/http/client/netty/DefaultHttpClient.java index 274a01739fd..22423b97a91 100644 --- a/http-client/src/main/java/io/micronaut/http/client/netty/DefaultHttpClient.java +++ b/http-client/src/main/java/io/micronaut/http/client/netty/DefaultHttpClient.java @@ -1284,14 +1284,14 @@ private NettyByteBody buildNettyRequest( boolean permitsBody, EventLoop eventLoop) throws HttpPostRequestEncoder.ErrorDataEncoderException { - if (!request.getHeaders().contains(io.micronaut.http.HttpHeaders.HOST)) { + if (!request.getHeaders().contains(HttpHeaderNames.HOST)) { request.getHeaders().set(HttpHeaderNames.HOST, getHostHeader(requestURI)); } if (permitsBody) { Optional body = request.getBody(); if (body.isPresent()) { - if (!request.getHeaders().contains(io.micronaut.http.HttpHeaders.CONTENT_TYPE)) { + if (!request.getHeaders().contains(HttpHeaderNames.CONTENT_TYPE)) { MediaType mediaType = request.getContentType().orElse(MediaType.APPLICATION_JSON_TYPE); request.getHeaders().set(HttpHeaderNames.CONTENT_TYPE, mediaType); } @@ -1971,6 +1971,14 @@ private static MessageBodyHandlerRegistry createDefaultMessageBodyHandlerRegistr } static boolean isSecureScheme(String scheme) { + // fast path + if (scheme.equals("http")) { + return false; + } + if (scheme.equals("https")) { + return true; + } + // actual case-insensitive check return io.micronaut.http.HttpRequest.SCHEME_HTTPS.equalsIgnoreCase(scheme) || SCHEME_WSS.equalsIgnoreCase(scheme); } diff --git a/http-netty/src/main/java/io/micronaut/http/netty/NettyHttpHeaders.java b/http-netty/src/main/java/io/micronaut/http/netty/NettyHttpHeaders.java index 0c740ce15a1..55019ca132e 100644 --- a/http-netty/src/main/java/io/micronaut/http/netty/NettyHttpHeaders.java +++ b/http-netty/src/main/java/io/micronaut/http/netty/NettyHttpHeaders.java @@ -104,6 +104,11 @@ public final boolean contains(String name) { return nettyHeaders.contains(name); } + @Override + public final boolean contains(CharSequence name) { + return nettyHeaders.contains(name); + } + @Override public Optional get(CharSequence name, ArgumentConversionContext conversionContext) { List values = nettyHeaders.getAll(name); diff --git a/http-netty/src/main/java/io/micronaut/http/netty/body/NettyCharSequenceBodyWriter.java b/http-netty/src/main/java/io/micronaut/http/netty/body/NettyCharSequenceBodyWriter.java index f52c55d6ae4..667612991cb 100644 --- a/http-netty/src/main/java/io/micronaut/http/netty/body/NettyCharSequenceBodyWriter.java +++ b/http-netty/src/main/java/io/micronaut/http/netty/body/NettyCharSequenceBodyWriter.java @@ -22,7 +22,6 @@ import io.micronaut.core.type.MutableHeaders; import io.micronaut.http.ByteBodyHttpResponse; import io.micronaut.http.ByteBodyHttpResponseWrapper; -import io.micronaut.http.HttpHeaders; import io.micronaut.http.HttpRequest; import io.micronaut.http.MediaType; import io.micronaut.http.MutableHttpHeaders; @@ -63,7 +62,7 @@ public ByteBodyHttpResponse write(ByteBufferFactory bufferFactory, Http ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, object) : ByteBufUtil.encodeString(ByteBufAllocator.DEFAULT, CharBuffer.wrap(object), charset); NettyHttpHeaders nettyHttpHeaders = (NettyHttpHeaders) headers; - if (!nettyHttpHeaders.contains(HttpHeaders.CONTENT_TYPE)) { + if (!nettyHttpHeaders.contains(HttpHeaderNames.CONTENT_TYPE)) { nettyHttpHeaders.set(HttpHeaderNames.CONTENT_TYPE, mediaType); } return ByteBodyHttpResponseWrapper.wrap(outgoingResponse, new AvailableNettyByteBody(byteBuf)); diff --git a/http-netty/src/main/java/io/micronaut/http/netty/body/NettyWritableBodyWriter.java b/http-netty/src/main/java/io/micronaut/http/netty/body/NettyWritableBodyWriter.java index 63ac58fd5e5..0a31a039fc1 100644 --- a/http-netty/src/main/java/io/micronaut/http/netty/body/NettyWritableBodyWriter.java +++ b/http-netty/src/main/java/io/micronaut/http/netty/body/NettyWritableBodyWriter.java @@ -26,7 +26,6 @@ import io.micronaut.core.type.MutableHeaders; import io.micronaut.http.ByteBodyHttpResponse; import io.micronaut.http.ByteBodyHttpResponseWrapper; -import io.micronaut.http.HttpHeaders; import io.micronaut.http.HttpRequest; import io.micronaut.http.MediaType; import io.micronaut.http.MutableHttpHeaders; @@ -41,6 +40,7 @@ import io.micronaut.runtime.ApplicationConfiguration; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufOutputStream; +import io.netty.handler.codec.http.HttpHeaderNames; import jakarta.inject.Singleton; import org.reactivestreams.Publisher; @@ -76,7 +76,7 @@ public boolean isBlocking() { @Override public ByteBodyHttpResponse write(ByteBufferFactory bufferFactory, HttpRequest request, MutableHttpResponse outgoingResponse, Argument type, MediaType mediaType, Writable object) throws CodecException { MutableHttpHeaders outgoingHeaders = outgoingResponse.getHeaders(); - if (mediaType != null && !outgoingHeaders.contains(HttpHeaders.CONTENT_TYPE)) { + if (mediaType != null && !outgoingHeaders.contains(HttpHeaderNames.CONTENT_TYPE)) { outgoingHeaders.contentType(mediaType); } ByteBufOutputStream outputStream = new ByteBufOutputStream(ByteBufAllocator.DEFAULT.buffer()); diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/body/AbstractFileBodyWriter.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/body/AbstractFileBodyWriter.java index 389e5d23985..23de9f08de3 100644 --- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/body/AbstractFileBodyWriter.java +++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/body/AbstractFileBodyWriter.java @@ -27,6 +27,7 @@ import io.micronaut.http.netty.body.AvailableNettyByteBody; import io.micronaut.http.server.netty.configuration.NettyHttpServerConfiguration; import io.micronaut.http.server.types.files.FileCustomizableResponseType; +import io.netty.handler.codec.http.HttpHeaderNames; import java.time.LocalDateTime; import java.time.ZonedDateTime; @@ -70,8 +71,8 @@ protected boolean handleIfModifiedAndHeaders(HttpRequest request, MutableHttp } } - if (!response.getHeaders().contains(HttpHeaders.CONTENT_TYPE)) { - response.header(HttpHeaders.CONTENT_TYPE, systemFile.getMediaType().toString()); + if (!response.getHeaders().contains(HttpHeaderNames.CONTENT_TYPE)) { + response.header(HttpHeaderNames.CONTENT_TYPE, systemFile.getMediaType().toString()); } setDateAndCacheHeaders(response, lastModified); systemFile.process(nettyResponse); @@ -86,7 +87,7 @@ protected void setDateAndCacheHeaders(MutableHttpResponse response, long lastMod // Date header MutableHttpHeaders headers = response.getHeaders(); LocalDateTime now = LocalDateTime.now(); - if (!headers.contains(HttpHeaders.DATE)) { + if (!headers.contains(HttpHeaderNames.DATE)) { headers.date(now); } diff --git a/http/src/main/java/io/micronaut/http/HttpHeaders.java b/http/src/main/java/io/micronaut/http/HttpHeaders.java index 7aeba92751c..26e43217ffa 100644 --- a/http/src/main/java/io/micronaut/http/HttpHeaders.java +++ b/http/src/main/java/io/micronaut/http/HttpHeaders.java @@ -622,6 +622,17 @@ public interface HttpHeaders extends Headers { X_AUTH_TOKEN )); + /** + * Whether the given key is contained within these values. + * + * @param name The key name + * @return True if it is + * @since 4.8.0 + */ + default boolean contains(CharSequence name) { + return contains(name.toString()); + } + /** * Obtain the date header. *