add better user/password handling in userInfo, for instance passwords with colon

This commit is contained in:
Jörg Prante 2018-10-22 23:13:42 +02:00
parent 2b284199bb
commit 412b6eaeb5
5 changed files with 111 additions and 51 deletions

View file

@ -1,6 +1,6 @@
group = org.xbib group = org.xbib
name = net name = net
version = 1.1.1 version = 1.1.3
jackson.version = 2.8.11 jackson.version = 2.8.11
junit.version = 4.12 junit.version = 4.12

View file

@ -248,7 +248,7 @@ public class URL implements Comparable<URL> {
public static URL from(String input, boolean resolve) { public static URL from(String input, boolean resolve) {
try { try {
return parser().parse(input, resolve); return parser().parse(input, resolve);
} catch (URLSyntaxException e) { } catch (URLSyntaxException | MalformedInputException | UnmappableCharacterException e) {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
} }
} }
@ -260,7 +260,7 @@ public class URL implements Comparable<URL> {
public static URL from(URL base, String spec) { public static URL from(URL base, String spec) {
try { try {
return new Resolver(base).resolve(spec); return new Resolver(base).resolve(spec);
} catch (URLSyntaxException e) { } catch (URLSyntaxException | MalformedInputException | UnmappableCharacterException e) {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
} }
} }
@ -330,6 +330,22 @@ public class URL implements Comparable<URL> {
return builder.userInfo; return builder.userInfo;
} }
public String getUser() {
if (builder.userInfo == null) {
return null;
}
Pair<String, String> p = indexOf(COLON_CHAR, builder.userInfo);
return decode(p.first);
}
public String getPassword() {
if (builder.userInfo == null) {
return null;
}
Pair<String, String> p = indexOf(COLON_CHAR, builder.userInfo);
return decode(p.second);
}
/** /**
* Get the host name ('www.example.com' or '192.168.0.1:8080' or '[fde2:d7de:302::]') of the {@code URL}. * Get the host name ('www.example.com' or '192.168.0.1:8080' or '[fde2:d7de:302::]') of the {@code URL}.
* @return the host name * @return the host name
@ -668,6 +684,13 @@ public class URL implements Comparable<URL> {
return str == null || str.isEmpty(); return str == null || str.isEmpty();
} }
private static Pair<String, String> indexOf(char ch, String input) {
int i = input.indexOf(ch);
String k = i >= 0 ? input.substring(0, i) : input;
String v = i >= 0 ? input.substring(i + 1) : null;
return new Pair<>(k, v);
}
@Override @Override
public int hashCode() { public int hashCode() {
return toString().hashCode(); return toString().hashCode();
@ -675,7 +698,7 @@ public class URL implements Comparable<URL> {
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
return other != null && other instanceof URL && toString().equals(other.toString()); return other instanceof URL && toString().equals(other.toString());
} }
@Override @Override
@ -751,6 +774,16 @@ public class URL implements Comparable<URL> {
return this; return this;
} }
public Builder userInfo(String user, String pass) {
try {
this.userInfo = PercentEncoders.getRegNameEncoder(charset).encode(user) + ':' +
PercentEncoders.getRegNameEncoder(charset).encode(pass);
} catch (MalformedInputException | UnmappableCharacterException e) {
throw new IllegalArgumentException(e);
}
return this;
}
public Builder host(String host) { public Builder host(String host) {
this.host = host; this.host = host;
this.protocolVersion = ProtocolVersion.NONE; this.protocolVersion = ProtocolVersion.NONE;
@ -935,11 +968,13 @@ public class URL implements Comparable<URL> {
builder = new Builder(); builder = new Builder();
} }
public URL parse(String input) throws URLSyntaxException { public URL parse(String input)
throws URLSyntaxException, MalformedInputException, UnmappableCharacterException {
return parse(input, true); return parse(input, true);
} }
public URL parse(String input, boolean resolve) throws URLSyntaxException { public URL parse(String input, boolean resolve)
throws URLSyntaxException, MalformedInputException, UnmappableCharacterException {
if (isNullOrEmpty(input)) { if (isNullOrEmpty(input)) {
return INVALID; return INVALID;
} }
@ -992,17 +1027,20 @@ public class URL implements Comparable<URL> {
return p.getSecond(); return p.getSecond();
} }
private String parseUserInfo(Builder builder, String input) { private String parseUserInfo(Builder builder, String input)
throws MalformedInputException, UnmappableCharacterException {
String remaining = input; String remaining = input;
int i = input.lastIndexOf(AT_CHAR); int i = input.lastIndexOf(AT_CHAR);
if (i > 0) { if (i > 0) {
remaining = input.substring(i + 1); remaining = input.substring(i + 1);
builder.userInfo(input.substring(0, i)); String userInfo = input.substring(0, i);
builder.userInfo(builder.percentDecoder.decode(userInfo));
} }
return remaining; return remaining;
} }
private void parseHostAndPort(Builder builder, String host, boolean resolve) throws URLSyntaxException { private void parseHostAndPort(Builder builder, String host, boolean resolve)
throws URLSyntaxException {
if (host.indexOf('[') == 0) { if (host.indexOf('[') == 0) {
int i = host.lastIndexOf(']'); int i = host.lastIndexOf(']');
if (i >= 0) { if (i >= 0) {
@ -1126,13 +1164,6 @@ public class URL implements Comparable<URL> {
builder.query(query); builder.query(query);
} }
} }
private Pair<String, String> indexOf(char ch, String input) {
int i = input.indexOf(ch);
String k = i >= 0 ? input.substring(0, i) : input;
String v = i >= 0 ? input.substring(i + 1) : null;
return new Pair<>(k, v);
}
} }
/** /**
@ -1147,7 +1178,8 @@ public class URL implements Comparable<URL> {
this.base = base; this.base = base;
} }
public URL resolve(String relative) throws URLSyntaxException { public URL resolve(String relative)
throws URLSyntaxException, MalformedInputException, UnmappableCharacterException {
if (relative == null) { if (relative == null) {
return null; return null;
} }
@ -1158,7 +1190,8 @@ public class URL implements Comparable<URL> {
return resolve(url); return resolve(url);
} }
public URL resolve(URL relative) throws URLSyntaxException { public URL resolve(URL relative)
throws URLSyntaxException {
if (relative == null || relative == INVALID) { if (relative == null || relative == INVALID) {
throw new URLSyntaxException("relative URL is invalid"); throw new URLSyntaxException("relative URL is invalid");
} }

View file

@ -10,17 +10,17 @@ import static org.junit.Assert.assertEquals;
public class URLBuilderTest { public class URLBuilderTest {
@Test @Test
public void testNoUrlParts() throws Exception { public void testNoUrlParts() {
assertUrl(URL.http().resolveFromHost("foo.com").toUrlString(), "http://foo.com"); assertUrl(URL.http().resolveFromHost("foo.com").toUrlString(), "http://foo.com");
} }
@Test @Test
public void testWithPort() throws Exception { public void testWithPort() {
assertUrl(URL.http().resolveFromHost("foo.com").port(33).toUrlString(), "http://foo.com:33"); assertUrl(URL.http().resolveFromHost("foo.com").port(33).toUrlString(), "http://foo.com:33");
} }
@Test @Test
public void testSimplePath() throws Exception { public void testSimplePath() {
assertUrl(URL.http().resolveFromHost("foo.com") assertUrl(URL.http().resolveFromHost("foo.com")
.pathSegment("seg1") .pathSegment("seg1")
.pathSegment("seg2") .pathSegment("seg2")
@ -29,7 +29,7 @@ public class URLBuilderTest {
} }
@Test @Test
public void testPathWithReserved() throws Exception { public void testPathWithReserved() {
// RFC 1738 S3.3 // RFC 1738 S3.3
assertUrl(URL.http().resolveFromHost("foo.com") assertUrl(URL.http().resolveFromHost("foo.com")
.pathSegment("seg/;?ment") .pathSegment("seg/;?ment")
@ -38,14 +38,14 @@ public class URLBuilderTest {
} }
@Test @Test
public void testPathSegments() throws Exception { public void testPathSegments() {
assertUrl(URL.http().resolveFromHost("foo.com") assertUrl(URL.http().resolveFromHost("foo.com")
.pathSegments("seg1", "seg2", "seg3") .pathSegments("seg1", "seg2", "seg3")
.toUrlString(), "http://foo.com/seg1/seg2/seg3"); .toUrlString(), "http://foo.com/seg1/seg2/seg3");
} }
@Test @Test
public void testMatrixWithReserved() throws Exception { public void testMatrixWithReserved() {
assertUrl(URL.http().resolveFromHost("foo.com") assertUrl(URL.http().resolveFromHost("foo.com")
.pathSegment("foo") .pathSegment("foo")
.matrixParam("foo", "bar") .matrixParam("foo", "bar")
@ -55,28 +55,28 @@ public class URLBuilderTest {
} }
@Test @Test
public void testUrlEncodedPathSegmentUtf8() throws Exception { public void testUrlEncodedPathSegmentUtf8() {
assertUrl(URL.http().resolveFromHost("foo.com") assertUrl(URL.http().resolveFromHost("foo.com")
.pathSegment("snowman").pathSegment("\u2603") .pathSegment("snowman").pathSegment("\u2603")
.toUrlString(), "http://foo.com/snowman/%E2%98%83"); .toUrlString(), "http://foo.com/snowman/%E2%98%83");
} }
@Test @Test
public void testUrlEncodedPathSegmentUtf8SurrogatePair() throws Exception { public void testUrlEncodedPathSegmentUtf8SurrogatePair() {
assertUrl(URL.http().resolveFromHost("foo.com") assertUrl(URL.http().resolveFromHost("foo.com")
.pathSegment("clef").pathSegment("\ud834\udd1e") .pathSegment("clef").pathSegment("\ud834\udd1e")
.toUrlString(), "http://foo.com/clef/%F0%9D%84%9E"); .toUrlString(), "http://foo.com/clef/%F0%9D%84%9E");
} }
@Test @Test
public void testQueryParamNoPath() throws Exception { public void testQueryParamNoPath() {
assertUrl(URL.http().resolveFromHost("foo.com") assertUrl(URL.http().resolveFromHost("foo.com")
.queryParam("foo", "bar") .queryParam("foo", "bar")
.toUrlString(), "http://foo.com?foo=bar"); .toUrlString(), "http://foo.com?foo=bar");
} }
@Test @Test
public void testQueryParamsDuplicated() throws Exception { public void testQueryParamsDuplicated() {
assertUrl(URL.http().resolveFromHost("foo.com") assertUrl(URL.http().resolveFromHost("foo.com")
.queryParam("foo", "bar") .queryParam("foo", "bar")
.queryParam("foo", "bar2") .queryParam("foo", "bar2")
@ -86,7 +86,7 @@ public class URLBuilderTest {
} }
@Test @Test
public void testEncodeQueryParams() throws Exception { public void testEncodeQueryParams() {
assertUrl(URL.http().resolveFromHost("foo.com") assertUrl(URL.http().resolveFromHost("foo.com")
.queryParam("foo", "bar&=#baz") .queryParam("foo", "bar&=#baz")
.queryParam("foo", "bar?/2") .queryParam("foo", "bar?/2")
@ -94,7 +94,7 @@ public class URLBuilderTest {
} }
@Test @Test
public void testEncodeQueryParamWithSpaceAndPlus() throws Exception { public void testEncodeQueryParamWithSpaceAndPlus() {
assertUrl(URL.http().resolveFromHost("foo.com") assertUrl(URL.http().resolveFromHost("foo.com")
.queryParam("foo", "spa ce") .queryParam("foo", "spa ce")
.queryParam("fo+o", "plus+") .queryParam("fo+o", "plus+")
@ -102,7 +102,7 @@ public class URLBuilderTest {
} }
@Test @Test
public void testPlusInVariousParts() throws Exception { public void testPlusInVariousParts() {
assertUrl(URL.http().resolveFromHost("foo.com") assertUrl(URL.http().resolveFromHost("foo.com")
.pathSegment("has+plus") .pathSegment("has+plus")
.matrixParam("plusMtx", "pl+us") .matrixParam("plusMtx", "pl+us")
@ -112,7 +112,7 @@ public class URLBuilderTest {
} }
@Test @Test
public void testFragment() throws Exception { public void testFragment() {
assertUrl(URL.http().resolveFromHost("foo.com") assertUrl(URL.http().resolveFromHost("foo.com")
.queryParam("foo", "bar") .queryParam("foo", "bar")
.fragment("#frag/?") .fragment("#frag/?")
@ -120,7 +120,7 @@ public class URLBuilderTest {
} }
@Test @Test
public void testAllParts() throws Exception { public void testAllParts() {
assertUrl(URL.https().resolveFromHost("foo.bar.com").port(3333) assertUrl(URL.https().resolveFromHost("foo.bar.com").port(3333)
.pathSegment("foo") .pathSegment("foo")
.pathSegment("bar") .pathSegment("bar")
@ -134,24 +134,24 @@ public class URLBuilderTest {
} }
@Test @Test
public void testSlashInHost() throws Exception { public void testSlashInHost() {
URL.http().resolveFromHost("/").toUrlString(); URL.http().resolveFromHost("/").toUrlString();
} }
@Test @Test
public void testGoogle() throws Exception { public void testGoogle() {
URL url = URL.https().resolveFromHost("google.com").build(); URL url = URL.https().resolveFromHost("google.com").build();
assertEquals("https://google.com", url.toString()); assertEquals("https://google.com", url.toString());
} }
@Test @Test
public void testBadIPv4LiteralDoesntChoke() throws Exception { public void testBadIPv4LiteralDoesntChoke() {
assertUrl(URL.http().resolveFromHost("300.100.50.1") assertUrl(URL.http().resolveFromHost("300.100.50.1")
.toUrlString(), "http://300.100.50.1"); .toUrlString(), "http://300.100.50.1");
} }
@Test @Test
public void testIPv4Literal() throws Exception { public void testIPv4Literal() {
if ("false".equals(System.getProperty("java.net.preferIPv6Addresses"))) { if ("false".equals(System.getProperty("java.net.preferIPv6Addresses"))) {
assertUrl(URL.http().resolveFromHost("127.0.0.1") assertUrl(URL.http().resolveFromHost("127.0.0.1")
.toUrlString(), "http://localhost"); .toUrlString(), "http://localhost");
@ -161,7 +161,7 @@ public class URLBuilderTest {
} }
@Test @Test
public void testIPv6LiteralLocalhost() throws Exception { public void testIPv6LiteralLocalhost() {
String s = URL.http().resolveFromHost("[::1]").toUrlString(); String s = URL.http().resolveFromHost("[::1]").toUrlString();
if ("true".equals(System.getProperty("java.net.preferIPv6Addresses"))) { if ("true".equals(System.getProperty("java.net.preferIPv6Addresses"))) {
assertEquals("http://[0:0:0:0:0:0:0:1]", s); assertEquals("http://[0:0:0:0:0:0:0:1]", s);
@ -171,7 +171,7 @@ public class URLBuilderTest {
} }
@Test @Test
public void testIPv6Literal() throws Exception { public void testIPv6Literal() {
if ("true".equals(System.getProperty("java.net.preferIPv6Addresses"))) { if ("true".equals(System.getProperty("java.net.preferIPv6Addresses"))) {
String s = URL.http().resolveFromHost("[2001:db8:85a3::8a2e:370:7334]") String s = URL.http().resolveFromHost("[2001:db8:85a3::8a2e:370:7334]")
.toUrlString(); .toUrlString();
@ -180,21 +180,21 @@ public class URLBuilderTest {
} }
@Test @Test
public void testEncodedRegNameSingleByte() throws Exception { public void testEncodedRegNameSingleByte() {
String s = URL.http().resolveFromHost("host?name;") String s = URL.http().resolveFromHost("host?name;")
.toUrlString(); .toUrlString();
assertEquals("http://host%3Fname;", s); assertEquals("http://host%3Fname;", s);
} }
@Test @Test
public void testEncodedRegNameMultiByte() throws Exception { public void testEncodedRegNameMultiByte() {
String s = URL.http().host("snow\u2603man") String s = URL.http().host("snow\u2603man")
.toUrlString(); .toUrlString();
assertEquals("http://snow%E2%98%83man", s); assertEquals("http://snow%E2%98%83man", s);
} }
@Test @Test
public void testThreePathSegments() throws Exception { public void testThreePathSegments() {
String s = URL.https().resolveFromHost("foo.com") String s = URL.https().resolveFromHost("foo.com")
.pathSegments("a", "b", "c") .pathSegments("a", "b", "c")
.toUrlString(); .toUrlString();
@ -202,7 +202,7 @@ public class URLBuilderTest {
} }
@Test @Test
public void testThreePathSegmentsWithQueryParams() throws Exception { public void testThreePathSegmentsWithQueryParams() {
String s = URL.https().resolveFromHost("foo.com") String s = URL.https().resolveFromHost("foo.com")
.pathSegments("a", "b", "c") .pathSegments("a", "b", "c")
.queryParam("foo", "bar") .queryParam("foo", "bar")
@ -211,7 +211,7 @@ public class URLBuilderTest {
} }
@Test @Test
public void testIntermingledMatrixParamsAndPathSegments() throws Exception { public void testIntermingledMatrixParamsAndPathSegments() {
String s = URL.http().resolveFromHost("foo.com") String s = URL.http().resolveFromHost("foo.com")
.pathSegments("seg1", "seg2") .pathSegments("seg1", "seg2")
.matrixParam("m1", "v1") .matrixParam("m1", "v1")
@ -222,7 +222,7 @@ public class URLBuilderTest {
} }
@Test @Test
public void testUseQueryParamAfterQuery() throws Exception { public void testUseQueryParamAfterQuery() {
String s = URL.http().resolveFromHost("foo.com") String s = URL.http().resolveFromHost("foo.com")
.query("q") .query("q")
.queryParam("foo", "bar") .queryParam("foo", "bar")
@ -231,7 +231,7 @@ public class URLBuilderTest {
} }
@Test @Test
public void testUseQueryAfterQueryParam() throws Exception { public void testUseQueryAfterQueryParam() {
String s = URL.http().resolveFromHost("foo.com") String s = URL.http().resolveFromHost("foo.com")
.queryParam("foo", "bar") .queryParam("foo", "bar")
.query("q") .query("q")
@ -240,7 +240,7 @@ public class URLBuilderTest {
} }
@Test @Test
public void testQueryWithNoSpecialChars() throws Exception { public void testQueryWithNoSpecialChars() {
String s = URL.http().resolveFromHost("foo.com") String s = URL.http().resolveFromHost("foo.com")
.query("q") .query("q")
.toUrlString(); .toUrlString();
@ -248,14 +248,14 @@ public class URLBuilderTest {
} }
@Test @Test
public void testQueryWithOkSpecialChars() throws Exception { public void testQueryWithOkSpecialChars() {
String s = URL.http().resolveFromHost("foo.com") String s = URL.http().resolveFromHost("foo.com")
.query("q?/&=").toUrlString(); .query("q?/&=").toUrlString();
assertEquals("http://foo.com?q?/&=", s); assertEquals("http://foo.com?q?/&=", s);
} }
@Test @Test
public void testQueryWithEscapedSpecialChars() throws Exception { public void testQueryWithEscapedSpecialChars() {
String s = URL.http().resolveFromHost("foo.com") String s = URL.http().resolveFromHost("foo.com")
.query("q#+").toUrlString(); .query("q#+").toUrlString();
assertEquals("http://foo.com?q%23%2B", s); assertEquals("http://foo.com?q%23%2B", s);
@ -268,7 +268,19 @@ public class URLBuilderTest {
assertEquals("https://google.com:8008/foobar", builder.build().toString()); assertEquals("https://google.com:8008/foobar", builder.build().toString());
} }
private void assertUrl(String urlString, String expected) throws Exception { @Test
public void testUserInfo(){
String s = URL.http().userInfo("foo:bar").host("foo.com").toUrlString();
assertEquals("http://foo:bar@foo.com", s);
s = URL.http().userInfo("foo:foo:bar").host("foo.com").toUrlString();
assertEquals("http://foo:foo:bar@foo.com", s);
s = URL.http().userInfo("foo:foo%3Abar").host("foo.com").toUrlString();
assertEquals("http://foo:foo%3Abar@foo.com", s);
s = URL.http().userInfo("foo", "foo:bar").host("foo.com").toUrlString();
assertEquals("http://foo:foo%3Abar@foo.com", s);
}
private void assertUrl(String urlString, String expected) {
assertEquals(expected, urlString); assertEquals(expected, urlString);
assertEquals(expected, URL.from(urlString).toExternalForm()); assertEquals(expected, URL.from(urlString).toExternalForm());
} }

View file

@ -369,6 +369,18 @@ public class URLParserTest {
assertEquals("plus+frag", url.getFragment()); assertEquals("plus+frag", url.getFragment());
} }
@Test
public void testUserInfo() throws Exception {
URL url = URL.parser().parse("http://foo:bar@foo.com/");
assertEquals("foo:bar", url.getUserInfo());
url = URL.parser().parse("http://foo:foo:bar@foo.com/");
assertEquals("foo:foo:bar", url.getUserInfo());
url = URL.parser().parse("http://foo:foo%3Abar@foo.com/");
assertEquals("foo:foo:bar", url.getUserInfo());
assertEquals("foo", url.getUser());
assertEquals("foo:bar", url.getPassword());
}
private void assertUrlCompatibility(String url) throws Exception { private void assertUrlCompatibility(String url) throws Exception {
String s = URL.from(url).toExternalForm(); String s = URL.from(url).toExternalForm();
assertEquals(s, URL.from(s).toExternalForm()); assertEquals(s, URL.from(s).toExternalForm());

View file

@ -5,6 +5,8 @@ import org.junit.Test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import java.net.URI; import java.net.URI;
import java.nio.charset.MalformedInputException;
import java.nio.charset.UnmappableCharacterException;
/** /**
*/ */
@ -98,7 +100,8 @@ public class URLResolverTest {
resolve("http://a/b/c/d;p?q", "http://e/f/g/h", "http://e/f/g/h"); resolve("http://a/b/c/d;p?q", "http://e/f/g/h", "http://e/f/g/h");
} }
private void resolve(String inputBase, String spec, String expected) throws URLSyntaxException { private void resolve(String inputBase, String spec, String expected)
throws URLSyntaxException, MalformedInputException, UnmappableCharacterException {
assertEquals(expected, URL.base(inputBase).resolve(spec).toExternalForm()); assertEquals(expected, URL.base(inputBase).resolve(spec).toExternalForm());
} }
} }