ArmeriaHttpUtil { public static String decodePath(String path) { if (path.indexOf('%') < 0) { return path; } final int len = path.length(); final byte[] buf = TemporaryThreadLocals.get().byteArray(len); int dstLen = 0; for (int i = 0; i < len; i++) { final char ch = path.charAt(i); if (ch != '%') { buf[dstLen++] = (byte) ((ch & 0xFF80) == 0 ? ch : 0xFF); continue; } final int hexEnd = i + 3; if (hexEnd > len) { buf[dstLen++] = (byte) 0xFF; break; } final int digit1 = decodeHexNibble(path.charAt(++i)); final int digit2 = decodeHexNibble(path.charAt(++i)); if (digit1 < 0 || digit2 < 0) { buf[dstLen++] = (byte) 0xFF; } else { buf[dstLen++] = (byte) ((digit1 << 4) | digit2); } } return new String(buf, 0, dstLen, StandardCharsets.UTF_8); } private  ArmeriaHttpUtil(); static String concatPaths(@Nullable String path1, @Nullable String path2); static String decodePath(String path); static boolean isAbsoluteUri(@Nullable String maybeUri); static boolean isInformational(@Nullable String statusText); static boolean isContentAlwaysEmptyWithValidation(HttpStatus status, HttpData content); static boolean isCorsPreflightRequest(com.linecorp.armeria.common.HttpRequest request); static void parseDirectives(String directives, BiConsumer<String, String> callback); static long parseDirectiveValueAsSeconds(@Nullable String value); static RequestHeaders toArmeriaRequestHeaders(ChannelHandlerContext ctx, Http2Headers headers,
                                                         boolean endOfStream, String scheme,
                                                         ServerConfig cfg); static HttpHeaders toArmeria(Http2Headers headers, boolean request, boolean endOfStream); static RequestHeaders toArmeria(ChannelHandlerContext ctx, HttpRequest in,
                                           ServerConfig cfg); static ResponseHeaders toArmeria(HttpResponse in); static HttpHeaders toArmeria(io.netty.handler.codec.http.HttpHeaders inHeaders); static void toArmeria(io.netty.handler.codec.http.HttpHeaders inHeaders, HttpHeadersBuilder out); static Http2Headers toNettyHttp2ServerHeaders(HttpHeaders inputHeaders); static Http2Headers toNettyHttp2ServerTrailer(HttpHeaders inputHeaders); static Http2Headers toNettyHttp2ClientHeader(HttpHeaders inputHeaders); static Http2Headers toNettyHttp2ClientTrailer(HttpHeaders inputHeaders); static void toNettyHttp1ServerHeader(
            HttpHeaders inputHeaders, io.netty.handler.codec.http.HttpHeaders outputHeaders); static void toNettyHttp1ServerTrailer(
            HttpHeaders inputHeaders, io.netty.handler.codec.http.HttpHeaders outputHeaders); static void toNettyHttp1ClientHeader(
            HttpHeaders inputHeaders, io.netty.handler.codec.http.HttpHeaders outputHeaders); static void toNettyHttp1ClientTrailer(
            HttpHeaders inputHeaders, io.netty.handler.codec.http.HttpHeaders outputHeaders); static ResponseHeaders setOrRemoveContentLength(ResponseHeaders headers, HttpData content,
                                                           HttpHeaders trailers); static boolean isTrailerBlacklisted(AsciiString name); static String authorityHeader(String host, int port, int defaultPort); static final Charset HTTP_DEFAULT_CONTENT_CHARSET; static final AsciiString HEADER_NAME_KEEP_ALIVE; static final AsciiString HEADER_NAME_PROXY_CONNECTION; static final String SERVER_HEADER; }
@Test void testDecodePath() throws Exception { final String pathThatDoesNotNeedDecode = "/foo_bar_baz"; assertThat(decodePath(pathThatDoesNotNeedDecode)).isSameAs(pathThatDoesNotNeedDecode); assertThat(decodePath("/foo%20bar\u007fbaz")).isEqualTo("/foo bar\u007fbaz"); assertThat(decodePath("/%C2%A2")).isEqualTo("/¢"); assertThat(decodePath("/%20\u0080")).isEqualTo("/ �"); assertThat(decodePath("/%")).isEqualTo("/�"); assertThat(decodePath("/%1")).isEqualTo("/�"); assertThat(decodePath("/%G0")).isEqualTo("/�"); assertThat(decodePath("/%0G")).isEqualTo("/�"); assertThat(decodePath("/%C3%28")).isEqualTo("/�("); }