fix percent decoding when parameter is query domain
This commit is contained in:
parent
3a4a3c692f
commit
183041acd3
9 changed files with 78 additions and 17 deletions
|
@ -1,5 +1,5 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = net
|
name = net
|
||||||
version = 4.0.0
|
version = 4.0.1
|
||||||
|
|
||||||
org.gradle.warning.mode = ALL
|
org.gradle.warning.mode = ALL
|
||||||
|
|
|
@ -4,34 +4,39 @@ import org.xbib.net.Parameter;
|
||||||
import org.xbib.net.ParameterBuilder;
|
import org.xbib.net.ParameterBuilder;
|
||||||
import org.xbib.net.PathNormalizer;
|
import org.xbib.net.PathNormalizer;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
public class PathDecoder {
|
public class PathDecoder {
|
||||||
|
|
||||||
private final String path;
|
private final String path;
|
||||||
|
|
||||||
private final String query;
|
private final String query;
|
||||||
|
|
||||||
|
private final Charset charset;
|
||||||
|
|
||||||
private final ParameterBuilder params;
|
private final ParameterBuilder params;
|
||||||
|
|
||||||
public PathDecoder(String pathAndQuery) {
|
public PathDecoder(String pathAndQuery, Charset charset) {
|
||||||
this(pathAndQuery, null);
|
this(pathAndQuery, null, charset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PathDecoder(String pathAndQuery, String queryString) {
|
public PathDecoder(String pathAndQuery, String queryString, Charset charset) {
|
||||||
|
this.charset = charset;
|
||||||
int pos = pathAndQuery.indexOf('?');
|
int pos = pathAndQuery.indexOf('?');
|
||||||
String path = pos > 0 ? pathAndQuery.substring(0, pos) : pathAndQuery;
|
String path = pos > 0 ? pathAndQuery.substring(0, pos) : pathAndQuery;
|
||||||
this.query = pos > 0 ? pathAndQuery.substring(pos + 1) : null;
|
this.query = pos > 0 ? pathAndQuery.substring(pos + 1) : null;
|
||||||
this.path = PathNormalizer.normalize(path);
|
this.path = PathNormalizer.normalize(path);
|
||||||
this.params = Parameter.builder().domain(Parameter.Domain.PATH).enablePercentDecoding();
|
this.params = Parameter.builder().domain(Parameter.Domain.PATH).enablePercentDecoding();
|
||||||
if (query != null) {
|
if (query != null) {
|
||||||
this.params.add(query);
|
this.params.add(query, charset);
|
||||||
}
|
}
|
||||||
if (queryString != null) {
|
if (queryString != null) {
|
||||||
this.params.add(queryString);
|
this.params.add(queryString, charset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parse(String queryString) {
|
public void parse(String queryString) {
|
||||||
this.params.add(queryString);
|
this.params.add(queryString, charset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String path() {
|
public String path() {
|
||||||
|
|
|
@ -4,6 +4,8 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.xbib.net.Parameter;
|
import org.xbib.net.Parameter;
|
||||||
import org.xbib.net.URL;
|
import org.xbib.net.URL;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
@ -11,27 +13,27 @@ class PathDecoderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPlusSign() throws Exception {
|
void testPlusSign() throws Exception {
|
||||||
PathDecoder decoder = new PathDecoder("/path?a=b+c", "d=e+f");
|
PathDecoder decoder = new PathDecoder("/path?a=b+c", "d=e+f", StandardCharsets.UTF_8);
|
||||||
assertEquals("[b c]", decoder.getParameter().getAll("a", Parameter.Domain.PATH).toString());
|
assertEquals("[b c]", decoder.getParameter().getAll("a", Parameter.Domain.PATH).toString());
|
||||||
assertEquals("[e f]", decoder.getParameter().getAll("d", Parameter.Domain.PATH).toString());
|
assertEquals("[e f]", decoder.getParameter().getAll("d", Parameter.Domain.PATH).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSlash() throws Exception {
|
void testSlash() throws Exception {
|
||||||
PathDecoder decoder = new PathDecoder("path/foo/bar/?a=b+c", "d=e+f");
|
PathDecoder decoder = new PathDecoder("path/foo/bar/?a=b+c", "d=e+f", StandardCharsets.UTF_8);
|
||||||
assertEquals("[b c]", decoder.getParameter().getAll("a", Parameter.Domain.PATH).toString());
|
assertEquals("[b c]", decoder.getParameter().getAll("a", Parameter.Domain.PATH).toString());
|
||||||
assertEquals("[e f]", decoder.getParameter().getAll("d", Parameter.Domain.PATH).toString());
|
assertEquals("[e f]", decoder.getParameter().getAll("d", Parameter.Domain.PATH).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDoubleSlashes() {
|
void testDoubleSlashes() {
|
||||||
PathDecoder decoder = new PathDecoder("//path", "");
|
PathDecoder decoder = new PathDecoder("//path", "", StandardCharsets.UTF_8);
|
||||||
assertEquals("/path", decoder.path());
|
assertEquals("/path", decoder.path());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSlashes() throws Exception {
|
void testSlashes() throws Exception {
|
||||||
PathDecoder decoder = new PathDecoder("//path?a=b+c", "d=e+f");
|
PathDecoder decoder = new PathDecoder("//path?a=b+c", "d=e+f", StandardCharsets.UTF_8);
|
||||||
assertEquals("/path", decoder.path());
|
assertEquals("/path", decoder.path());
|
||||||
assertEquals("[b c]", decoder.getParameter().getAll("a", Parameter.Domain.PATH).toString());
|
assertEquals("[b c]", decoder.getParameter().getAll("a", Parameter.Domain.PATH).toString());
|
||||||
assertEquals("[e f]", decoder.getParameter().getAll("d", Parameter.Domain.PATH).toString());
|
assertEquals("[e f]", decoder.getParameter().getAll("d", Parameter.Domain.PATH).toString());
|
||||||
|
@ -39,7 +41,7 @@ class PathDecoderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPlusPercent() throws Exception {
|
void testPlusPercent() throws Exception {
|
||||||
PathDecoder decoder = new PathDecoder("//path?a=b%2Bc", "d=e%2Bf");
|
PathDecoder decoder = new PathDecoder("//path?a=b%2Bc", "d=e%2Bf", StandardCharsets.UTF_8);
|
||||||
assertEquals("/path", decoder.path());
|
assertEquals("/path", decoder.path());
|
||||||
assertEquals("[b+c]", decoder.getParameter().getAll("a", Parameter.Domain.PATH).toString());
|
assertEquals("[b+c]", decoder.getParameter().getAll("a", Parameter.Domain.PATH).toString());
|
||||||
assertEquals("[e+f]", decoder.getParameter().getAll("d", Parameter.Domain.PATH).toString());
|
assertEquals("[e+f]", decoder.getParameter().getAll("d", Parameter.Domain.PATH).toString());
|
||||||
|
@ -53,7 +55,7 @@ class PathDecoderTest {
|
||||||
assertNull(url.getPort());
|
assertNull(url.getPort());
|
||||||
assertEquals("/pdfconverter/index.gtpl", url.getPath());
|
assertEquals("/pdfconverter/index.gtpl", url.getPath());
|
||||||
assertNull(url.getFragment());
|
assertNull(url.getFragment());
|
||||||
PathDecoder decoder = new PathDecoder(requestURI);
|
PathDecoder decoder = new PathDecoder(requestURI, StandardCharsets.UTF_8);
|
||||||
if (url.getQuery() != null) {
|
if (url.getQuery() != null) {
|
||||||
decoder.parse(url.getDecodedQuery());
|
decoder.parse(url.getDecodedQuery());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.xbib.net;
|
package org.xbib.net;
|
||||||
|
|
||||||
|
import java.nio.charset.MalformedInputException;
|
||||||
|
import java.nio.charset.UnmappableCharacterException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
@ -102,9 +104,17 @@ public class Parameter implements Iterable<Pair<String, Object>>, Comparable<Par
|
||||||
} else {
|
} else {
|
||||||
object = null;
|
object = null;
|
||||||
}
|
}
|
||||||
return object != null ? object.toString() : null;
|
return object != null ? decodeIfQueryDomain(domain, object.toString()) : null;
|
||||||
|
}
|
||||||
|
return object != null ? decodeIfQueryDomain(domain, object instanceof String ? (String) object : object.toString()) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String decodeIfQueryDomain(Domain domain, String string) throws ParameterException {
|
||||||
|
try {
|
||||||
|
return Domain.QUERY == domain ? builder().percentDecode(string) : string;
|
||||||
|
} catch (UnmappableCharacterException | MalformedInputException e) {
|
||||||
|
throw new ParameterException(e);
|
||||||
}
|
}
|
||||||
return object != null ? object instanceof String ? (String) object : object.toString() : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
|
@ -14,6 +14,8 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.xbib.datastructures.common.ImmutableList;
|
import org.xbib.datastructures.common.ImmutableList;
|
||||||
import org.xbib.datastructures.common.MultiMap;
|
import org.xbib.datastructures.common.MultiMap;
|
||||||
import org.xbib.datastructures.common.Pair;
|
import org.xbib.datastructures.common.Pair;
|
||||||
|
@ -107,11 +109,25 @@ public class ParameterBuilder implements PairValidator {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String percentEncode(CharSequence text) throws UnmappableCharacterException, MalformedInputException {
|
||||||
|
if (percentEncoder == null) {
|
||||||
|
charset(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
return percentEncoder.encode(text);
|
||||||
|
}
|
||||||
|
|
||||||
public ParameterBuilder percentDecode(PercentDecoder percentDecoder) {
|
public ParameterBuilder percentDecode(PercentDecoder percentDecoder) {
|
||||||
this.percentDecoder = percentDecoder;
|
this.percentDecoder = percentDecoder;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String percentDecode(CharSequence text) throws UnmappableCharacterException, MalformedInputException {
|
||||||
|
if (percentDecoder == null) {
|
||||||
|
charset(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
return percentDecoder.decode(text);
|
||||||
|
}
|
||||||
|
|
||||||
public ParameterBuilder enablePercentEncoding() {
|
public ParameterBuilder enablePercentEncoding() {
|
||||||
this.enablePercentEncoding = true;
|
this.enablePercentEncoding = true;
|
||||||
charset(StandardCharsets.UTF_8);
|
charset(StandardCharsets.UTF_8);
|
||||||
|
@ -222,7 +238,11 @@ public class ParameterBuilder implements PairValidator {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParameterBuilder add(String percentEncodedQueryString) {
|
public ParameterBuilder add(String percentEncodedQueryString, Charset charset) {
|
||||||
|
Objects.requireNonNull(charset);
|
||||||
|
if (this.charset == null || !this.charset.equals(charset)) {
|
||||||
|
charset(charset);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
decodeQueryString(percentEncodedQueryString);
|
decodeQueryString(percentEncodedQueryString);
|
||||||
} catch (MalformedInputException | UnmappableCharacterException e) {
|
} catch (MalformedInputException | UnmappableCharacterException e) {
|
||||||
|
@ -235,6 +255,9 @@ public class ParameterBuilder implements PairValidator {
|
||||||
if (enableSort) {
|
if (enableSort) {
|
||||||
list.sort(Comparator.comparing(Pair::getKey));
|
list.sort(Comparator.comparing(Pair::getKey));
|
||||||
}
|
}
|
||||||
|
if (percentDecoder == null) {
|
||||||
|
charset(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
String queryString = null;
|
String queryString = null;
|
||||||
if (enableQueryString) {
|
if (enableQueryString) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -3,6 +3,10 @@ package org.xbib.net;
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class ParameterException extends Exception {
|
public class ParameterException extends Exception {
|
||||||
|
|
||||||
|
public ParameterException(Exception e) {
|
||||||
|
super(e);
|
||||||
|
}
|
||||||
|
|
||||||
public ParameterException(String message) {
|
public ParameterException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,17 @@ public class ParameterTest {
|
||||||
parameter.getAsQueryString());
|
parameter.getAsQueryString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQueryParameterDecoding() throws ParameterException {
|
||||||
|
Parameter parameter = Parameter.builder()
|
||||||
|
.domain(Parameter.Domain.QUERY)
|
||||||
|
.add("value1", "Hello%20J%C3%B6rg") // query parameter, no query string mode
|
||||||
|
.add("value2=Hello%20J%C3%B6rg", StandardCharsets.UTF_8) // query string mode
|
||||||
|
.build();
|
||||||
|
assertEquals("Hello Jörg", parameter.getAsString("value1", Parameter.Domain.QUERY));
|
||||||
|
assertEquals("Hello Jörg", parameter.getAsString("value2", Parameter.Domain.QUERY));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParameters() {
|
public void testParameters() {
|
||||||
Map<String, Object> map = Map.of(
|
Map<String, Object> map = Map.of(
|
||||||
|
|
|
@ -56,6 +56,11 @@ class PercentDecoderTest {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testJoerg() throws UnmappableCharacterException, MalformedInputException {
|
||||||
|
assertEquals("Hello Jörg", decoder.decode("Hello%20J%C3%B6rg"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRandomStrings() throws MalformedInputException, UnmappableCharacterException {
|
void testRandomStrings() throws MalformedInputException, UnmappableCharacterException {
|
||||||
PercentEncoder encoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8);
|
PercentEncoder encoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8);
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.xbib.net;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.CodingErrorAction;
|
import java.nio.charset.CodingErrorAction;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -498,7 +499,7 @@ class URLParserTest {
|
||||||
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());
|
||||||
assertEquals(s, new java.net.URL(url).toExternalForm());
|
assertEquals(s, URI.create(url).toURL().toExternalForm());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertRoundTrip(String url) {
|
private void assertRoundTrip(String url) {
|
||||||
|
|
Loading…
Reference in a new issue