mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[http] Properly escape + character in query string (#17042)
* [http] Properly escape + character in query string Signed-off-by: Jan N. Klug <github@klug.nrw> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
b87e22bc0c
commit
e5510f66bf
@ -369,7 +369,7 @@ public class HttpThingHandler extends BaseThingHandler implements HttpStatusList
|
|||||||
private void sendHttpValue(String commandUrl, String command, boolean isRetry) {
|
private void sendHttpValue(String commandUrl, String command, boolean isRetry) {
|
||||||
try {
|
try {
|
||||||
// format URL
|
// format URL
|
||||||
URI uri = Util.uriFromString(String.format(commandUrl, new Date(), command));
|
URI uri = Util.uriFromString(Util.wrappedStringFormat(commandUrl, new Date(), command));
|
||||||
|
|
||||||
// build request
|
// build request
|
||||||
rateLimitedHttpClient.newPriorityRequest(uri, config.commandMethod, command, config.contentType)
|
rateLimitedHttpClient.newPriorityRequest(uri, config.commandMethod, command, config.contentType)
|
||||||
|
@ -18,6 +18,8 @@ import java.net.URI;
|
|||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
@ -34,8 +36,10 @@ import org.eclipse.jetty.http.HttpField;
|
|||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class Util {
|
public class Util {
|
||||||
|
|
||||||
|
public static final Pattern FORMAT_REPLACE_PATTERN = Pattern.compile("%\\d\\$[^%]+");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create a log string from a {@link org.eclipse.jetty.client.api.Request}
|
* Create a log string from a {@link org.eclipse.jetty.client.api.Request}
|
||||||
*
|
*
|
||||||
* @param request the request to log
|
* @param request the request to log
|
||||||
* @return the string representing the request
|
* @return the string representing the request
|
||||||
@ -51,17 +55,33 @@ public class Util {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create an URI from a string, escaping all necessary characters
|
* Create a URI from a string, escaping all necessary characters
|
||||||
*
|
*
|
||||||
* @param s the URI as unescaped string
|
* @param s the URI as unescaped string
|
||||||
* @return URI corresponding to the input string
|
* @return URI corresponding to the input string
|
||||||
* @throws MalformedURLException if parameter is not an URL
|
* @throws MalformedURLException if parameter is not a URL
|
||||||
* @throws URISyntaxException if parameter could not be converted to an URI
|
* @throws URISyntaxException if parameter could not be converted to a URI
|
||||||
*/
|
*/
|
||||||
public static URI uriFromString(String s) throws MalformedURLException, URISyntaxException {
|
public static URI uriFromString(String s) throws MalformedURLException, URISyntaxException {
|
||||||
URL url = new URL(s);
|
URL url = new URL(s);
|
||||||
URI uri = new URI(url.getProtocol(), url.getUserInfo(), IDN.toASCII(url.getHost()), url.getPort(),
|
URI uri = new URI(url.getProtocol(), url.getUserInfo(), IDN.toASCII(url.getHost()), url.getPort(),
|
||||||
url.getPath(), url.getQuery(), url.getRef());
|
url.getPath(), url.getQuery(), url.getRef());
|
||||||
return URI.create(uri.toASCIIString());
|
return URI.create(uri.toASCIIString().replace("+", "%2B"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a string using {@link String#format(String, Object...)} but allow non-format percent characters
|
||||||
|
*
|
||||||
|
* The {@param inputString} is checked for format patterns ({@code %<index>$<format>}) and passes only those to the
|
||||||
|
* {@link String#format(String, Object...)} method. This avoids format errors due to other percent characters in the
|
||||||
|
* string.
|
||||||
|
*
|
||||||
|
* @param inputString the input string, potentially containing format instructions
|
||||||
|
* @param params an array of parameters to be passed to the splitted input string
|
||||||
|
* @return the formatted string
|
||||||
|
*/
|
||||||
|
public static String wrappedStringFormat(String inputString, Object... params) {
|
||||||
|
Matcher replaceMatcher = FORMAT_REPLACE_PATTERN.matcher(inputString);
|
||||||
|
return replaceMatcher.replaceAll(matchResult -> String.format(matchResult.group(), params));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,7 @@ public class RefreshingUrlCache {
|
|||||||
|
|
||||||
// format URL
|
// format URL
|
||||||
try {
|
try {
|
||||||
URI uri = Util.uriFromString(String.format(this.url, new Date()));
|
URI uri = Util.uriFromString(Util.wrappedStringFormat(this.url, new Date()));
|
||||||
logger.trace("Requesting refresh (retry={}) from '{}' with timeout {}ms", isRetry, uri, timeout);
|
logger.trace("Requesting refresh (retry={}) from '{}' with timeout {}ms", isRetry, uri, timeout);
|
||||||
|
|
||||||
httpClient.newRequest(uri, httpMethod, httpContent, httpContentType).thenAccept(request -> {
|
httpClient.newRequest(uri, httpMethod, httpContent, httpContentType).thenAccept(request -> {
|
||||||
|
@ -12,11 +12,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.http;
|
package org.openhab.binding.http;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.openhab.binding.http.internal.Util;
|
import org.openhab.binding.http.internal.Util;
|
||||||
|
|
||||||
@ -31,30 +34,61 @@ public class UtilTest {
|
|||||||
@Test
|
@Test
|
||||||
public void uriUTF8InHostnameEncodeTest() throws MalformedURLException, URISyntaxException {
|
public void uriUTF8InHostnameEncodeTest() throws MalformedURLException, URISyntaxException {
|
||||||
String s = "https://foöo.bar/zhu.html?str=zin&tzz=678";
|
String s = "https://foöo.bar/zhu.html?str=zin&tzz=678";
|
||||||
Assertions.assertEquals("https://xn--foo-tna.bar/zhu.html?str=zin&tzz=678", Util.uriFromString(s).toString());
|
assertEquals("https://xn--foo-tna.bar/zhu.html?str=zin&tzz=678", Util.uriFromString(s).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void uriUTF8InPathEncodeTest() throws MalformedURLException, URISyntaxException {
|
public void uriUTF8InPathEncodeTest() throws MalformedURLException, URISyntaxException {
|
||||||
String s = "https://foo.bar/zül.html?str=zin";
|
String s = "https://foo.bar/zül.html?str=zin";
|
||||||
Assertions.assertEquals("https://foo.bar/z%C3%BCl.html?str=zin", Util.uriFromString(s).toString());
|
assertEquals("https://foo.bar/z%C3%BCl.html?str=zin", Util.uriFromString(s).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void uriUTF8InQueryEncodeTest() throws MalformedURLException, URISyntaxException {
|
public void uriUTF8InQueryEncodeTest() throws MalformedURLException, URISyntaxException {
|
||||||
String s = "https://foo.bar/zil.html?str=zän";
|
String s = "https://foo.bar/zil.html?str=zän";
|
||||||
Assertions.assertEquals("https://foo.bar/zil.html?str=z%C3%A4n", Util.uriFromString(s).toString());
|
assertEquals("https://foo.bar/zil.html?str=z%C3%A4n", Util.uriFromString(s).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void uriSpaceInPathEncodeTest() throws MalformedURLException, URISyntaxException {
|
public void uriSpaceInPathEncodeTest() throws MalformedURLException, URISyntaxException {
|
||||||
String s = "https://foo.bar/z l.html?str=zun";
|
String s = "https://foo.bar/z l.html?str=zun";
|
||||||
Assertions.assertEquals("https://foo.bar/z%20l.html?str=zun", Util.uriFromString(s).toString());
|
assertEquals("https://foo.bar/z%20l.html?str=zun", Util.uriFromString(s).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void uriSpaceInQueryEncodeTest() throws MalformedURLException, URISyntaxException {
|
public void uriSpaceInQueryEncodeTest() throws MalformedURLException, URISyntaxException {
|
||||||
String s = "https://foo.bar/zzl.html?str=z n";
|
String s = "https://foo.bar/zzl.html?str=z n";
|
||||||
Assertions.assertEquals("https://foo.bar/zzl.html?str=z%20n", Util.uriFromString(s).toString());
|
assertEquals("https://foo.bar/zzl.html?str=z%20n", Util.uriFromString(s).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void uriPlusInQueryEncodeTest() throws MalformedURLException, URISyntaxException {
|
||||||
|
String s = "https://foo.bar/zzl.html?str=z+n";
|
||||||
|
assertEquals("https://foo.bar/zzl.html?str=z%2Bn", Util.uriFromString(s).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void uriAlreadyPartlyEscapedTest() throws MalformedURLException, URISyntaxException {
|
||||||
|
String s = "https://foo.bar/zzl.html?p=field%2Bvalue&foostatus=This is a test String&date=2024- 07-01";
|
||||||
|
assertEquals(
|
||||||
|
"https://foo.bar/zzl.html?p=field%252Bvalue&foostatus=This%20is%20a%20test%20String&date=2024-%20%2007-01",
|
||||||
|
Util.uriFromString(s).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void wrappedStringFormatDateTest() {
|
||||||
|
String formatString = "https://foo.bar/zzl.html?p=field%2Bvalue&date=%1$tY-%1$4tm-%1$td";
|
||||||
|
Date testDate = Date.from(Instant.parse("2024-07-01T10:00:00.000Z"));
|
||||||
|
assertEquals("https://foo.bar/zzl.html?p=field%2Bvalue&date=2024- 07-01",
|
||||||
|
Util.wrappedStringFormat(formatString, testDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void wrappedStringFormatDateAndCommandTest() {
|
||||||
|
String formatString = "https://foo.bar/zzl.html?p=field%2Bvalue&foostatus=%2$s&date=%1$tY-%1$4tm-%1$td";
|
||||||
|
Date testDate = Date.from(Instant.parse("2024-07-01T10:00:00.000Z"));
|
||||||
|
String testCommand = "This is a test String";
|
||||||
|
assertEquals("https://foo.bar/zzl.html?p=field%2Bvalue&foostatus=This is a test String&date=2024- 07-01",
|
||||||
|
Util.wrappedStringFormat(formatString, testDate, testCommand));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user