Skip to content
Permalink
Browse files
CXF-8688: Accomodate some RFC 3986 checks to filter out invalid URIs (#…
  • Loading branch information
reta committed Apr 16, 2022
1 parent 27cb61a commit fe512bc456d7d3ed2e24c1d378fa767e86951cbc
Showing 5 changed files with 98 additions and 1 deletion.
@@ -0,0 +1,70 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.cxf.jaxrs.impl;

import java.net.URI;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.jaxrs.utils.HttpUtils;

final class Rfc3986UriValidator {
private static final String SCHEME = "(?i)(http|https):";

private static final String USERINFO = "([^@\\[/?#]*)";

private static final String HOST = "([^/?#]*)";

private static final String PATH = "([^?#]*)";

private static final String QUERY = "([^#]*)";

private static final String LAST = "#(.*)";

private static final Pattern HTTP_URL = Pattern.compile("^" + SCHEME
+ "(//(" + USERINFO + "@)?" + HOST + ")?" + PATH
+ "(\\?" + QUERY + ")?" + "(" + LAST + ")?");

private Rfc3986UriValidator() {
}

/**
* Validate the HTTP URL according to https://datatracker.ietf.org/doc/html/rfc3986#appendix-B
* @param uri HTTP schemed URI to validate
* @return "true" if URI matches RFC-3986 validation rules, "false" otherwise
*/
public static boolean validate(final URI uri) {
// Only validate the HTTP(s) URIs
if (HttpUtils.isHttpScheme(uri.getScheme())) {
final Matcher matcher = HTTP_URL.matcher(uri.toString());
if (matcher.matches()) {
final String host = matcher.group(5);
// There is no host component in the HTTP URI, it is required
return !(StringUtils.isEmpty(host));
} else {
return false;
}
} else {
// not HTTP URI, skipping
return true;
}
}
}
@@ -124,7 +124,13 @@ private URI doBuild(boolean fromEncoded, boolean encodePathSlash, Object... valu

UriParts parts = doBuildUriParts(fromEncoded, encodePathSlash, false, values);
try {
return buildURI(fromEncoded, parts.path, parts.query, parts.fragment);
final URI uri = buildURI(fromEncoded, parts.path, parts.query, parts.fragment);

if (!Rfc3986UriValidator.validate(uri)) {
throw new UriBuilderException("[" + uri + "] is not a valid HTTP URL");
}

return uri;
} catch (URISyntaxException ex) {
throw new UriBuilderException("URI can not be built", ex);
}
@@ -99,6 +99,8 @@ public final class HttpUtils {
new HashSet<>(Arrays.asList(new String[]{"GET", "HEAD", "OPTIONS", "TRACE"}));
private static final Set<String> KNOWN_HTTP_VERBS_WITH_NO_RESPONSE_CONTENT =
new HashSet<>(Arrays.asList(new String[]{"HEAD", "OPTIONS"}));

private static final Pattern HTTP_SCHEME_PATTERN = Pattern.compile("^(?i)(http|https)$");

private HttpUtils() {
}
@@ -703,4 +705,8 @@ public static boolean isMethodWithNoRequestContent(String method) {
public static boolean isMethodWithNoResponseContent(String method) {
return KNOWN_HTTP_VERBS_WITH_NO_RESPONSE_CONTENT.contains(method);
}

public static boolean isHttpScheme(final String scheme) {
return scheme != null && HTTP_SCHEME_PATTERN.matcher(scheme).matches();
}
}
@@ -33,6 +33,7 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

@@ -140,6 +141,12 @@ public void testInvalidString() throws Exception {
}
}

@Test
public void invalidUrlsNoHost() {
assertThrows(UriBuilderException.class, () -> Link.fromUri("http://@").build());
assertThrows(UriBuilderException.class, () -> Link.fromUri("http://:@").build());
}

@Path("resource")
public static class TestResource {
@POST
@@ -32,6 +32,7 @@
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriBuilderException;

import org.apache.cxf.jaxrs.resources.Book;
import org.apache.cxf.jaxrs.resources.BookStore;
@@ -43,6 +44,7 @@
import org.junit.rules.ExpectedException;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;

public class UriBuilderImplTest {
@@ -1975,4 +1977,10 @@ public void pathParamFromBadTemplate() {
.build();
assertEquals("/%7B", uri.toString());
}

@Test
public void invalidUrlsNoHost() {
assertThrows(UriBuilderException.class, () -> UriBuilder.fromUri("http://@").build());
assertThrows(UriBuilderException.class, () -> UriBuilder.fromUri("http://:@").build());
}
}

0 comments on commit fe512bc

Please sign in to comment.