]> git.basschouten.com Git - openhab-addons.git/commitdiff
[http] Properly escape + character in query string (#17042)
authorJ-N-K <github@klug.nrw>
Fri, 12 Jul 2024 20:02:44 +0000 (22:02 +0200)
committerGitHub <noreply@github.com>
Fri, 12 Jul 2024 20:02:44 +0000 (22:02 +0200)
* [http] Properly escape + character in query string

Signed-off-by: Jan N. Klug <github@klug.nrw>
bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/HttpThingHandler.java
bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/Util.java
bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/http/RefreshingUrlCache.java
bundles/org.openhab.binding.http/src/test/java/org/openhab/binding/http/UtilTest.java

index 8947852e11057c01c296ec8ea107e6e569b90c05..19bfbb9bfbb87088455e9a0e97714064763aacbe 100644 (file)
@@ -369,7 +369,7 @@ public class HttpThingHandler extends BaseThingHandler implements HttpStatusList
     private void sendHttpValue(String commandUrl, String command, boolean isRetry) {
         try {
             // format URL
-            URI uri = Util.uriFromString(String.format(commandUrl, new Date(), command));
+            URI uri = Util.uriFromString(Util.wrappedStringFormat(commandUrl, new Date(), command));
 
             // build request
             rateLimitedHttpClient.newPriorityRequest(uri, config.commandMethod, command, config.contentType)
index b0c6ff69e9a6944594236a2fd9bb959963dc637f..2c490ba5d5a714b7331f0a247afc0c02f02be141 100644 (file)
@@ -18,6 +18,8 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
@@ -34,8 +36,10 @@ import org.eclipse.jetty.http.HttpField;
 @NonNullByDefault
 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
      * @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
      * @return URI corresponding to the input string
-     * @throws MalformedURLException if parameter is not an URL
-     * @throws URISyntaxException if parameter could not be converted to an URI
+     * @throws MalformedURLException if parameter is not a URL
+     * @throws URISyntaxException if parameter could not be converted to a URI
      */
     public static URI uriFromString(String s) throws MalformedURLException, URISyntaxException {
         URL url = new URL(s);
         URI uri = new URI(url.getProtocol(), url.getUserInfo(), IDN.toASCII(url.getHost()), url.getPort(),
                 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));
     }
 }
index 2045f34cdfd6af3d2366147b76f4b7d87fcceb49..da2fbc264e3c64b600857f84d1a92b743f19f670 100644 (file)
@@ -108,7 +108,7 @@ public class RefreshingUrlCache {
 
         // format URL
         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);
 
             httpClient.newRequest(uri, httpMethod, httpContent, httpContentType).thenAccept(request -> {
index b68e799a6a21f07aecccb5c494bd5188bbe09e38..b5a8d3e40a1bda7ffb01b70914fb8f3bc1dd1180 100644 (file)
  */
 package org.openhab.binding.http;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
 import java.net.MalformedURLException;
 import java.net.URISyntaxException;
+import java.time.Instant;
+import java.util.Date;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.openhab.binding.http.internal.Util;
 
@@ -31,30 +34,61 @@ public class UtilTest {
     @Test
     public void uriUTF8InHostnameEncodeTest() throws MalformedURLException, URISyntaxException {
         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
     public void uriUTF8InPathEncodeTest() throws MalformedURLException, URISyntaxException {
         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
     public void uriUTF8InQueryEncodeTest() throws MalformedURLException, URISyntaxException {
         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
     public void uriSpaceInPathEncodeTest() throws MalformedURLException, URISyntaxException {
         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
     public void uriSpaceInQueryEncodeTest() throws MalformedURLException, URISyntaxException {
         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));
     }
 }