You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ck...@apache.org on 2021/04/20 20:55:00 UTC
[httpcomponents-core] 01/02: Improved parsing and formatting of URI
components
This is an automated email from the ASF dual-hosted git repository.
ckozak pushed a commit to branch 5.1.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git
commit 95bc9e22773983aeda21e8a66c24077672d24fe7
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Thu Apr 8 18:30:00 2021 +0200
Improved parsing and formatting of URI components
---
.../java/org/apache/hc/core5/http/HttpHost.java | 73 +++-----------
.../main/java/org/apache/hc/core5/net/Host.java | 69 ++++++++++---
.../java/org/apache/hc/core5/net/URIAuthority.java | 109 ++++++++-------------
.../java/org/apache/hc/core5/net/URISupport.java | 58 +++++++++++
.../java/org/apache/hc/core5/net/TestHost.java | 5 -
5 files changed, 167 insertions(+), 147 deletions(-)
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpHost.java b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpHost.java
index 40a0a47..d2b38e5 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/HttpHost.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/HttpHost.java
@@ -35,8 +35,8 @@ import java.util.Locale;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.net.Host;
import org.apache.hc.core5.net.NamedEndpoint;
-import org.apache.hc.core5.net.Ports;
import org.apache.hc.core5.net.URIAuthority;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.LangUtils;
@@ -59,18 +59,8 @@ public final class HttpHost implements NamedEndpoint, Serializable {
/** The default scheme is "http". */
public static final URIScheme DEFAULT_SCHEME = URIScheme.HTTP;
- /** The host to use. */
- private final String hostname;
-
- /** The lowercase host, for {@link #equals} and {@link #hashCode}. */
- private final String lcHostname;
-
- /** The port to use, defaults to -1 if not set. */
- private final int port;
-
- /** The scheme (lowercased) */
private final String schemeName;
-
+ private final Host host;
private final InetAddress address;
/**
@@ -91,14 +81,9 @@ public final class HttpHost implements NamedEndpoint, Serializable {
* @since 5.0
*/
public HttpHost(final String scheme, final InetAddress address, final String hostname, final int port) {
- this.hostname = Args.containsNoBlanks(hostname, "Host name");
- this.port = Ports.checkWithDefault(port);
- this.lcHostname = hostname.toLowerCase(Locale.ROOT);
- if (scheme != null) {
- this.schemeName = scheme.toLowerCase(Locale.ROOT);
- } else {
- this.schemeName = DEFAULT_SCHEME.id;
- }
+ Args.containsNoBlanks(hostname, "Host name");
+ this.host = new Host(hostname, port);
+ this.schemeName = scheme != null ? scheme.toLowerCase(Locale.ROOT) : DEFAULT_SCHEME.id;
this.address = address;
}
@@ -166,20 +151,8 @@ public final class HttpHost implements NamedEndpoint, Serializable {
}
text = text.substring(schemeIdx + 3);
}
- int port = -1;
- final int portIdx = text.lastIndexOf(":");
- if (portIdx > 0) {
- try {
- port = Integer.parseInt(text.substring(portIdx + 1));
- } catch (final NumberFormatException ex) {
- throw new URISyntaxException(s, "invalid port");
- }
- text = text.substring(0, portIdx);
- }
- if (TextUtils.containsBlanks(text)) {
- throw new URISyntaxException(s, "hostname contains blanks");
- }
- return new HttpHost(scheme, null, text, port);
+ final Host host = Host.create(text);
+ return new HttpHost(scheme, host);
}
/**
@@ -290,7 +263,7 @@ public final class HttpHost implements NamedEndpoint, Serializable {
*/
@Override
public String getHostName() {
- return this.hostname;
+ return this.host.getHostName();
}
/**
@@ -300,7 +273,7 @@ public final class HttpHost implements NamedEndpoint, Serializable {
*/
@Override
public int getPort() {
- return this.port;
+ return this.host.getPort();
}
/**
@@ -332,11 +305,7 @@ public final class HttpHost implements NamedEndpoint, Serializable {
final StringBuilder buffer = new StringBuilder();
buffer.append(this.schemeName);
buffer.append("://");
- buffer.append(this.hostname);
- if (this.port != -1) {
- buffer.append(':');
- buffer.append(this.port);
- }
+ buffer.append(this.host.toString());
return buffer.toString();
}
@@ -347,15 +316,7 @@ public final class HttpHost implements NamedEndpoint, Serializable {
* @return the host string, for example {@code localhost:8080}
*/
public String toHostString() {
- if (this.port != -1) {
- //the highest port number is 65535, which is length 6 with the addition of the colon
- final StringBuilder buffer = new StringBuilder(this.hostname.length() + 6);
- buffer.append(this.hostname);
- buffer.append(":");
- buffer.append(this.port);
- return buffer.toString();
- }
- return this.hostname;
+ return this.host.toString();
}
@@ -372,10 +333,9 @@ public final class HttpHost implements NamedEndpoint, Serializable {
}
if (obj instanceof HttpHost) {
final HttpHost that = (HttpHost) obj;
- return this.lcHostname.equals(that.lcHostname)
- && this.port == that.port
- && this.schemeName.equals(that.schemeName)
- && LangUtils.equals(this.address, that.address);
+ return this.schemeName.equals(that.schemeName) &&
+ this.host.equals(that.host) &&
+ LangUtils.equals(this.address, that.address);
}
return false;
}
@@ -386,10 +346,9 @@ public final class HttpHost implements NamedEndpoint, Serializable {
@Override
public int hashCode() {
int hash = LangUtils.HASH_SEED;
- hash = LangUtils.hashCode(hash, this.lcHostname);
- hash = LangUtils.hashCode(hash, this.port);
hash = LangUtils.hashCode(hash, this.schemeName);
- hash = LangUtils.hashCode(hash, address);
+ hash = LangUtils.hashCode(hash, this.host);
+ hash = LangUtils.hashCode(hash, this.address);
return hash;
}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/Host.java b/httpcore5/src/main/java/org/apache/hc/core5/net/Host.java
index f03b790..d915b0e 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/net/Host.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/net/Host.java
@@ -35,6 +35,7 @@ import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.LangUtils;
import org.apache.hc.core5.util.TextUtils;
+import org.apache.hc.core5.util.Tokenizer;
/**
* Component that holds all details needed to describe a network connection
@@ -52,28 +53,66 @@ public final class Host implements NamedEndpoint, Serializable {
public Host(final String name, final int port) {
super();
- this.name = Args.containsNoBlanks(name, "Host name");
- this.port = Ports.check(port);
+ this.name = Args.notNull(name, "Host name");
+ this.port = Ports.checkWithDefault(port);
this.lcName = this.name.toLowerCase(Locale.ROOT);
}
- public static Host create(final String s) throws URISyntaxException {
- Args.notEmpty(s, "HTTP Host");
- final int portIdx = s.lastIndexOf(":");
+ static Host parse(final CharSequence s, final Tokenizer.Cursor cursor) throws URISyntaxException {
+ final Tokenizer tokenizer = Tokenizer.INSTANCE;
+ final String hostName = tokenizer.parseContent(s, cursor, URISupport.PORT_SEPARATORS);
+ String portText = null;
+ if (!cursor.atEnd() && s.charAt(cursor.getPos()) == ':') {
+ cursor.updatePos(cursor.getPos() + 1);
+ portText = tokenizer.parseContent(s, cursor, URISupport.TERMINATORS);
+ }
final int port;
- if (portIdx > 0) {
+ if (!TextUtils.isBlank(portText)) {
try {
- port = Integer.parseInt(s.substring(portIdx + 1));
+ port = Integer.parseInt(portText);
} catch (final NumberFormatException ex) {
- throw new URISyntaxException(s, "invalid port");
+ throw URISupport.createException(s, cursor, "Port is invalid");
}
- final String hostname = s.substring(0, portIdx);
- if (TextUtils.containsBlanks(hostname)) {
- throw new URISyntaxException(s, "hostname contains blanks");
- }
- return new Host(hostname, port);
+ } else {
+ port = -1;
+ }
+ return new Host(hostName, port);
+ }
+
+ static Host parse(final CharSequence s) throws URISyntaxException {
+ final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
+ return parse(s, cursor);
+ }
+
+ static void format(final StringBuilder buf, final NamedEndpoint endpoint) {
+ buf.append(endpoint.getHostName());
+ if (endpoint.getPort() != -1) {
+ buf.append(":");
+ buf.append(endpoint.getPort());
+ }
+ }
+
+ static void format(final StringBuilder buf, final Host host) {
+ format(buf, (NamedEndpoint) host);
+ }
+
+ static String format(final Host host) {
+ final StringBuilder buf = new StringBuilder();
+ format(buf, host);
+ return buf.toString();
+ }
+
+ public static Host create(final String s) throws URISyntaxException {
+ Args.notEmpty(s, "HTTP Host");
+ final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
+ final Host host = parse(s, cursor);
+ if (TextUtils.isBlank(host.getHostName())) {
+ throw URISupport.createException(s, cursor, "Hostname is invalid");
+ }
+ if (!cursor.atEnd()) {
+ throw URISupport.createException(s, cursor, "Unexpected content");
}
- throw new URISyntaxException(s, "port not found");
+ return host;
}
@Override
@@ -108,7 +147,7 @@ public final class Host implements NamedEndpoint, Serializable {
@Override
public String toString() {
- return name + ":" + port;
+ return format(this);
}
}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/URIAuthority.java b/httpcore5/src/main/java/org/apache/hc/core5/net/URIAuthority.java
index 2907f9d..939f27d 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/net/URIAuthority.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/net/URIAuthority.java
@@ -29,11 +29,10 @@ package org.apache.hc.core5.net;
import java.io.Serializable;
import java.net.URISyntaxException;
-import java.util.BitSet;
-import java.util.Locale;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.LangUtils;
import org.apache.hc.core5.util.TextUtils;
import org.apache.hc.core5.util.Tokenizer;
@@ -48,36 +47,13 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
private static final long serialVersionUID = 1L;
private final String userInfo;
- private final String hostname;
- private final int port;
-
- private static final BitSet HOST_SEPARATORS = new BitSet(256);
- private static final BitSet PORT_SEPARATORS = new BitSet(256);
- private static final BitSet TERMINATORS = new BitSet(256);
-
- static {
- TERMINATORS.set('/');
- TERMINATORS.set('#');
- TERMINATORS.set('?');
- HOST_SEPARATORS.or(TERMINATORS);
- HOST_SEPARATORS.set('@');
- PORT_SEPARATORS.or(TERMINATORS);
- PORT_SEPARATORS.set(':');
- }
-
- static URISyntaxException createException(
- final CharSequence input, final Tokenizer.Cursor cursor, final String reason) {
- return new URISyntaxException(
- input.subSequence(cursor.getLowerBound(), cursor.getUpperBound()).toString(),
- reason,
- cursor.getPos());
- }
+ private final Host host;
static URIAuthority parse(final CharSequence s, final Tokenizer.Cursor cursor) throws URISyntaxException {
final Tokenizer tokenizer = Tokenizer.INSTANCE;
String userInfo = null;
final int initPos = cursor.getPos();
- final String token = tokenizer.parseContent(s, cursor, HOST_SEPARATORS);
+ final String token = tokenizer.parseContent(s, cursor, URISupport.HOST_SEPARATORS);
if (!cursor.atEnd() && s.charAt(cursor.getPos()) == '@') {
cursor.updatePos(cursor.getPos() + 1);
if (!TextUtils.isBlank(token)) {
@@ -87,23 +63,8 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
//Rewind
cursor.updatePos(initPos);
}
- final String hostName = tokenizer.parseContent(s, cursor, PORT_SEPARATORS);
- String portText = null;
- if (!cursor.atEnd() && s.charAt(cursor.getPos()) == ':') {
- cursor.updatePos(cursor.getPos() + 1);
- portText = tokenizer.parseContent(s, cursor, TERMINATORS);
- }
- final int port;
- if (!TextUtils.isBlank(portText)) {
- try {
- port = Integer.parseInt(portText);
- } catch (final NumberFormatException ex) {
- throw createException(s, cursor, "Authority port is invalid");
- }
- } else {
- port = -1;
- }
- return new URIAuthority(userInfo, hostName.toLowerCase(Locale.ROOT), port, true);
+ final Host host = Host.parse(s, cursor);
+ return new URIAuthority(userInfo, host);
}
static URIAuthority parse(final CharSequence s) throws URISyntaxException {
@@ -112,15 +73,11 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
}
static void format(final StringBuilder buf, final URIAuthority uriAuthority) {
- if (uriAuthority.userInfo != null) {
- buf.append(uriAuthority.userInfo);
+ if (uriAuthority.getUserInfo() != null) {
+ buf.append(uriAuthority.getUserInfo());
buf.append("@");
}
- buf.append(uriAuthority.hostname);
- if (uriAuthority.port != -1) {
- buf.append(":");
- buf.append(uriAuthority.port);
- }
+ Host.format(buf, uriAuthority);
}
static String format(final URIAuthority uriAuthority) {
@@ -134,31 +91,45 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
* If the port parameter is outside the specified range of valid port values, which is between 0 and
* 65535, inclusive. {@code -1} indicates the scheme default port.
*/
- private URIAuthority(final String userInfo, final String hostname, final int port, final boolean internal) {
+ public URIAuthority(final String userInfo, final String hostname, final int port) {
super();
this.userInfo = userInfo;
- this.hostname = hostname;
- this.port = Ports.checkWithDefault(port);
+ this.host = new Host(hostname, port);
+ }
+
+ public URIAuthority(final String hostname, final int port) {
+ this(null, hostname, port);
}
/**
- * @throws IllegalArgumentException
- * If the port parameter is outside the specified range of valid port values, which is between 0 and
- * 65535, inclusive. {@code -1} indicates the scheme default port.
+ * @since 5.2
*/
- public URIAuthority(final String userInfo, final String hostname, final int port) {
+ public URIAuthority(final String userInfo, final Host host) {
super();
+ Args.notNull(host, "Host");
this.userInfo = userInfo;
- this.hostname = hostname != null ? hostname.toLowerCase(Locale.ROOT) : null;
- this.port = Ports.checkWithDefault(port);
+ this.host = host;
}
- public URIAuthority(final String hostname, final int port) {
- this(null, hostname, port);
+ /**
+ * @since 5.2
+ */
+ public URIAuthority(final Host host) {
+ this(null, host);
+ }
+
+ /**
+ * @since 5.2
+ */
+ public URIAuthority(final String userInfo, final NamedEndpoint endpoint) {
+ super();
+ Args.notNull(endpoint, "Endpoint");
+ this.userInfo = userInfo;
+ this.host = new Host(endpoint.getHostName(), endpoint.getPort());
}
public URIAuthority(final NamedEndpoint namedEndpoint) {
- this(null, namedEndpoint.getHostName(), namedEndpoint.getPort());
+ this(null, namedEndpoint);
}
/**
@@ -171,7 +142,7 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
final URIAuthority uriAuthority = parse(s, cursor);
if (!cursor.atEnd()) {
- throw createException(s, cursor, "Unexpected content");
+ throw URISupport.createException(s, cursor, "Unexpected content");
}
return uriAuthority;
}
@@ -186,12 +157,12 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
@Override
public String getHostName() {
- return hostname;
+ return host.getHostName();
}
@Override
public int getPort() {
- return port;
+ return host.getPort();
}
@Override
@@ -207,8 +178,7 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
if (obj instanceof URIAuthority) {
final URIAuthority that = (URIAuthority) obj;
return LangUtils.equals(this.userInfo, that.userInfo) &&
- LangUtils.equals(this.hostname, that.hostname) &&
- this.port == that.port;
+ LangUtils.equals(this.host, that.host);
}
return false;
}
@@ -217,8 +187,7 @@ public final class URIAuthority implements NamedEndpoint, Serializable {
public int hashCode() {
int hash = LangUtils.HASH_SEED;
hash = LangUtils.hashCode(hash, userInfo);
- hash = LangUtils.hashCode(hash, hostname);
- hash = LangUtils.hashCode(hash, port);
+ hash = LangUtils.hashCode(hash, host);
return hash;
}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/URISupport.java b/httpcore5/src/main/java/org/apache/hc/core5/net/URISupport.java
new file mode 100644
index 0000000..6948dc8
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/net/URISupport.java
@@ -0,0 +1,58 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.core5.net;
+
+import java.net.URISyntaxException;
+import java.util.BitSet;
+
+import org.apache.hc.core5.util.Tokenizer;
+
+final class URISupport {
+
+ static final BitSet HOST_SEPARATORS = new BitSet(256);
+ static final BitSet PORT_SEPARATORS = new BitSet(256);
+ static final BitSet TERMINATORS = new BitSet(256);
+
+ static {
+ TERMINATORS.set('/');
+ TERMINATORS.set('#');
+ TERMINATORS.set('?');
+ HOST_SEPARATORS.or(TERMINATORS);
+ HOST_SEPARATORS.set('@');
+ PORT_SEPARATORS.or(TERMINATORS);
+ PORT_SEPARATORS.set(':');
+ }
+
+ static URISyntaxException createException(
+ final CharSequence input, final Tokenizer.Cursor cursor, final String reason) {
+ return new URISyntaxException(
+ input.subSequence(cursor.getLowerBound(), cursor.getUpperBound()).toString(),
+ reason,
+ cursor.getPos());
+ }
+
+}
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestHost.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestHost.java
index 23c5880..71f35d5 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestHost.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestHost.java
@@ -55,11 +55,6 @@ public class TestHost {
Assert.fail("NullPointerException should have been thrown");
} catch (final NullPointerException expected) {
}
- try {
- new Host("blah", -1);
- Assert.fail("IllegalArgumentException should have been thrown");
- } catch (final IllegalArgumentException expected) {
- }
}
@Test