fix percent decoding when parameter is query domain

This commit is contained in:
Jörg Prante 2023-12-07 17:26:48 +01:00
parent 3a4a3c692f
commit 183041acd3
9 changed files with 78 additions and 17 deletions

View file

@ -1,5 +1,5 @@
group = org.xbib
name = net
version = 4.0.0
version = 4.0.1
org.gradle.warning.mode = ALL

View file

@ -4,34 +4,39 @@ import org.xbib.net.Parameter;
import org.xbib.net.ParameterBuilder;
import org.xbib.net.PathNormalizer;
import java.nio.charset.Charset;
public class PathDecoder {
private final String path;
private final String query;
private final Charset charset;
private final ParameterBuilder params;
public PathDecoder(String pathAndQuery) {
this(pathAndQuery, null);
public PathDecoder(String pathAndQuery, Charset charset) {
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('?');
String path = pos > 0 ? pathAndQuery.substring(0, pos) : pathAndQuery;
this.query = pos > 0 ? pathAndQuery.substring(pos + 1) : null;
this.path = PathNormalizer.normalize(path);
this.params = Parameter.builder().domain(Parameter.Domain.PATH).enablePercentDecoding();
if (query != null) {
this.params.add(query);
this.params.add(query, charset);
}
if (queryString != null) {
this.params.add(queryString);
this.params.add(queryString, charset);
}
}
public void parse(String queryString) {
this.params.add(queryString);
this.params.add(queryString, charset);
}
public String path() {

View file

@ -4,6 +4,8 @@ import org.junit.jupiter.api.Test;
import org.xbib.net.Parameter;
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.assertNull;
@ -11,27 +13,27 @@ class PathDecoderTest {
@Test
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("[e f]", decoder.getParameter().getAll("d", Parameter.Domain.PATH).toString());
}
@Test
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("[e f]", decoder.getParameter().getAll("d", Parameter.Domain.PATH).toString());
}
@Test
void testDoubleSlashes() {
PathDecoder decoder = new PathDecoder("//path", "");
PathDecoder decoder = new PathDecoder("//path", "", StandardCharsets.UTF_8);
assertEquals("/path", decoder.path());
}
@Test
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("[b c]", decoder.getParameter().getAll("a", Parameter.Domain.PATH).toString());
assertEquals("[e f]", decoder.getParameter().getAll("d", Parameter.Domain.PATH).toString());
@ -39,7 +41,7 @@ class PathDecoderTest {
@Test
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("[b+c]", decoder.getParameter().getAll("a", Parameter.Domain.PATH).toString());
assertEquals("[e+f]", decoder.getParameter().getAll("d", Parameter.Domain.PATH).toString());
@ -53,7 +55,7 @@ class PathDecoderTest {
assertNull(url.getPort());
assertEquals("/pdfconverter/index.gtpl", url.getPath());
assertNull(url.getFragment());
PathDecoder decoder = new PathDecoder(requestURI);
PathDecoder decoder = new PathDecoder(requestURI, StandardCharsets.UTF_8);
if (url.getQuery() != null) {
decoder.parse(url.getDecodedQuery());
}

View file

@ -1,5 +1,7 @@
package org.xbib.net;
import java.nio.charset.MalformedInputException;
import java.nio.charset.UnmappableCharacterException;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
@ -102,9 +104,17 @@ public class Parameter implements Iterable<Pair<String, Object>>, Comparable<Par
} else {
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")

View file

@ -14,6 +14,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import org.xbib.datastructures.common.ImmutableList;
import org.xbib.datastructures.common.MultiMap;
import org.xbib.datastructures.common.Pair;
@ -107,11 +109,25 @@ public class ParameterBuilder implements PairValidator {
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) {
this.percentDecoder = percentDecoder;
return this;
}
public String percentDecode(CharSequence text) throws UnmappableCharacterException, MalformedInputException {
if (percentDecoder == null) {
charset(StandardCharsets.UTF_8);
}
return percentDecoder.decode(text);
}
public ParameterBuilder enablePercentEncoding() {
this.enablePercentEncoding = true;
charset(StandardCharsets.UTF_8);
@ -222,7 +238,11 @@ public class ParameterBuilder implements PairValidator {
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 {
decodeQueryString(percentEncodedQueryString);
} catch (MalformedInputException | UnmappableCharacterException e) {
@ -235,6 +255,9 @@ public class ParameterBuilder implements PairValidator {
if (enableSort) {
list.sort(Comparator.comparing(Pair::getKey));
}
if (percentDecoder == null) {
charset(StandardCharsets.UTF_8);
}
String queryString = null;
if (enableQueryString) {
try {

View file

@ -3,6 +3,10 @@ package org.xbib.net;
@SuppressWarnings("serial")
public class ParameterException extends Exception {
public ParameterException(Exception e) {
super(e);
}
public ParameterException(String message) {
super(message);
}

View file

@ -77,6 +77,17 @@ public class ParameterTest {
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
public void testParameters() {
Map<String, Object> map = Map.of(

View file

@ -56,6 +56,11 @@ class PercentDecoderTest {
});
}
@Test
void testJoerg() throws UnmappableCharacterException, MalformedInputException {
assertEquals("Hello Jörg", decoder.decode("Hello%20J%C3%B6rg"));
}
@Test
void testRandomStrings() throws MalformedInputException, UnmappableCharacterException {
PercentEncoder encoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8);

View file

@ -3,6 +3,7 @@ package org.xbib.net;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
@ -498,7 +499,7 @@ class URLParserTest {
private void assertUrlCompatibility(String url) throws Exception {
String s = URL.from(url).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) {