mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-24 10:19:03 +00:00
Merge pull request #46751 from nosan
* pr/46751: Polish "Use StringUtils.uriDecode where feasible" Use StringUtils.uriDecode where feasible Closes gh-46751
This commit is contained in:
+63
-68
@@ -16,17 +16,16 @@
|
||||
|
||||
package org.springframework.boot.loader.net.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HexFormat;
|
||||
|
||||
/**
|
||||
* Utility to decode URL strings.
|
||||
* Utility to decode URL strings. Copied frm Spring Framework's {@code StringUtils} as we
|
||||
* cannot depend on it in the loader.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public final class UrlDecoder {
|
||||
@@ -35,73 +34,69 @@ public final class UrlDecoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the given string by decoding URL {@code '%'} escapes. This method should be
|
||||
* identical in behavior to the {@code decode} method in the internal
|
||||
* {@code sun.net.www.ParseUtil} JDK class.
|
||||
* @param string the string to decode
|
||||
* @return the decoded string
|
||||
* Decode the given encoded URI component value by replacing each "<i>{@code %xy}</i>"
|
||||
* sequence with a hexadecimal representation of the character in
|
||||
* {@link StandardCharsets#UTF_8 UTF-8}, leaving other characters unmodified.
|
||||
* @param source the encoded URI component value
|
||||
* @return the decoded value
|
||||
*/
|
||||
public static String decode(String string) {
|
||||
int length = string.length();
|
||||
if ((length == 0) || (string.indexOf('%') < 0)) {
|
||||
return string;
|
||||
public static String decode(String source) {
|
||||
return decode(source, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the given encoded URI component value by replacing each "<i>{@code %xy}</i>"
|
||||
* sequence with a hexadecimal representation of the character in the specified
|
||||
* character encoding, leaving other characters unmodified.
|
||||
* @param source the encoded URI component value
|
||||
* @param charset the character encoding to use to decode the "<i>{@code %xy}</i>"
|
||||
* sequences
|
||||
* @return the decoded value
|
||||
*/
|
||||
public static String decode(String source, Charset charset) {
|
||||
int length = source.length();
|
||||
int firstPercentIndex = source.indexOf('%');
|
||||
if (length == 0 || firstPercentIndex < 0) {
|
||||
return source;
|
||||
}
|
||||
StringBuilder result = new StringBuilder(length);
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(length);
|
||||
CharBuffer charBuffer = CharBuffer.allocate(length);
|
||||
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder()
|
||||
.onMalformedInput(CodingErrorAction.REPORT)
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT);
|
||||
int index = 0;
|
||||
while (index < length) {
|
||||
char ch = string.charAt(index);
|
||||
if (ch != '%') {
|
||||
result.append(ch);
|
||||
if (index + 1 >= length) {
|
||||
return result.toString();
|
||||
|
||||
StringBuilder output = new StringBuilder(length);
|
||||
output.append(source, 0, firstPercentIndex);
|
||||
byte[] bytes = null;
|
||||
int i = firstPercentIndex;
|
||||
while (i < length) {
|
||||
char ch = source.charAt(i);
|
||||
if (ch == '%') {
|
||||
try {
|
||||
if (bytes == null) {
|
||||
bytes = new byte[(length - i) / 3];
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
while (i + 2 < length && ch == '%') {
|
||||
bytes[pos++] = (byte) HexFormat.fromHexDigits(source, i + 1, i + 3);
|
||||
i += 3;
|
||||
if (i < length) {
|
||||
ch = source.charAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (i < length && ch == '%') {
|
||||
throw new IllegalArgumentException("Incomplete trailing escape (%) pattern");
|
||||
}
|
||||
|
||||
output.append(new String(bytes, 0, pos, charset));
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
|
||||
}
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
index = fillByteBuffer(byteBuffer, string, index, length);
|
||||
decodeToCharBuffer(byteBuffer, charBuffer, decoder);
|
||||
result.append(charBuffer.flip());
|
||||
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static int fillByteBuffer(ByteBuffer byteBuffer, String string, int index, int length) {
|
||||
byteBuffer.clear();
|
||||
do {
|
||||
byteBuffer.put(unescape(string, index));
|
||||
index += 3;
|
||||
}
|
||||
while (index < length && string.charAt(index) == '%');
|
||||
byteBuffer.flip();
|
||||
return index;
|
||||
}
|
||||
|
||||
private static byte unescape(String string, int index) {
|
||||
try {
|
||||
return (byte) Integer.parseInt(string, index + 1, index + 3, 16);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
private static void decodeToCharBuffer(ByteBuffer byteBuffer, CharBuffer charBuffer, CharsetDecoder decoder) {
|
||||
decoder.reset();
|
||||
charBuffer.clear();
|
||||
assertNoError(decoder.decode(byteBuffer, charBuffer, true));
|
||||
assertNoError(decoder.flush(charBuffer));
|
||||
}
|
||||
|
||||
private static void assertNoError(CoderResult result) {
|
||||
if (result.isError()) {
|
||||
throw new IllegalArgumentException("Error decoding percent encoded characters");
|
||||
else {
|
||||
output.append(ch);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-41
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.boot.opentelemetry.autoconfigure;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -129,7 +128,7 @@ public class OpenTelemetryResourceAttributes {
|
||||
if (index > 0) {
|
||||
String key = attribute.substring(0, index);
|
||||
String value = attribute.substring(index + 1);
|
||||
attributes.put(key.trim(), decode(value.trim()));
|
||||
attributes.put(key.trim(), StringUtils.uriDecode(value.trim(), StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
String otelServiceName = getEnv("OTEL_SERVICE_NAME");
|
||||
@@ -143,43 +142,4 @@ public class OpenTelemetryResourceAttributes {
|
||||
return this.systemEnvironment.apply(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a percent-encoded string. Converts sequences like '%HH' (where HH
|
||||
* represents hexadecimal digits) back into their literal representations.
|
||||
* <p>
|
||||
* Inspired by {@code org.apache.commons.codec.net.PercentCodec}.
|
||||
* @param value value to decode
|
||||
* @return the decoded string
|
||||
*/
|
||||
private static String decode(String value) {
|
||||
if (value.indexOf('%') < 0) {
|
||||
return value;
|
||||
}
|
||||
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(bytes.length);
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
byte b = bytes[i];
|
||||
if (b != '%') {
|
||||
out.write(b);
|
||||
continue;
|
||||
}
|
||||
int u = decodeHex(bytes, i + 1);
|
||||
int l = decodeHex(bytes, i + 2);
|
||||
if (u >= 0 && l >= 0) {
|
||||
out.write((u << 4) + l);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to decode percent-encoded characters at index %d in the value: '%s'".formatted(i,
|
||||
value));
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
return out.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static int decodeHex(byte[] bytes, int index) {
|
||||
return (index < bytes.length) ? Character.digit(bytes[index], 16) : -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -137,7 +137,7 @@ class OpenTelemetryResourceAttributesTests {
|
||||
void illegalArgumentExceptionShouldBeThrownWhenDecodingIllegalHexCharPercentEncodedValue() {
|
||||
this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "key=abc%ß");
|
||||
assertThatIllegalArgumentException().isThrownBy(this::getAttributes)
|
||||
.withMessage("Failed to decode percent-encoded characters at index 3 in the value: 'abc%ß'");
|
||||
.withMessage("Incomplete trailing escape (%) pattern");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -150,7 +150,7 @@ class OpenTelemetryResourceAttributesTests {
|
||||
void illegalArgumentExceptionShouldBeThrownWhenDecodingInvalidPercentEncodedValue() {
|
||||
this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "key=%");
|
||||
assertThatIllegalArgumentException().isThrownBy(this::getAttributes)
|
||||
.withMessage("Failed to decode percent-encoded characters at index 0 in the value: '%'");
|
||||
.withMessage("Incomplete trailing escape (%) pattern");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user