Browse Source

Support parsing locales without country, with variant

Prior to this commit, the parsing algorithm in
`StringUtils#parseLocaleString` would skip empty tokens, turning
`en__VARIANT` into `en_VARIANT` when parsed.

This commit ensures that such cases are now supported and that more
invalid formats are rejected by the parser.

Fixes gh-29248
pull/29282/head
Brian Clozel 4 years ago
parent
commit
3b91eea0b7
  1. 63
      spring-core/src/main/java/org/springframework/util/StringUtils.java
  2. 27
      spring-core/src/test/java/org/springframework/util/StringUtilsTests.java

63
spring-core/src/main/java/org/springframework/util/StringUtils.java

@ -34,6 +34,7 @@ import java.util.Set;
import java.util.StringJoiner; import java.util.StringJoiner;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.stream.Collectors;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -821,15 +822,14 @@ public abstract class StringUtils {
*/ */
@Nullable @Nullable
public static Locale parseLocale(String localeValue) { public static Locale parseLocale(String localeValue) {
String[] tokens = tokenizeLocaleSource(localeValue); if (!localeValue.contains("_") && !localeValue.contains(" ")) {
if (tokens.length == 1) {
validateLocalePart(localeValue); validateLocalePart(localeValue);
Locale resolved = Locale.forLanguageTag(localeValue); Locale resolved = Locale.forLanguageTag(localeValue);
if (resolved.getLanguage().length() > 0) { if (resolved.getLanguage().length() > 0) {
return resolved; return resolved;
} }
} }
return parseLocaleTokens(localeValue, tokens); return parseLocaleString(localeValue);
} }
/** /**
@ -848,38 +848,35 @@ public abstract class StringUtils {
*/ */
@Nullable @Nullable
public static Locale parseLocaleString(String localeString) { public static Locale parseLocaleString(String localeString) {
return parseLocaleTokens(localeString, tokenizeLocaleSource(localeString)); if (localeString.equals("")) {
} return null;
private static String[] tokenizeLocaleSource(String localeSource) {
return tokenizeToStringArray(localeSource, "_ ", false, false);
}
@Nullable
private static Locale parseLocaleTokens(String localeString, String[] tokens) {
String language = (tokens.length > 0 ? tokens[0] : "");
String country = (tokens.length > 1 ? tokens[1] : "");
validateLocalePart(language);
validateLocalePart(country);
String variant = "";
if (tokens.length > 2) {
// There is definitely a variant, and it is everything after the country
// code sans the separator between the country code and the variant.
int endIndexOfCountryCode = localeString.indexOf(country, language.length()) + country.length();
// Strip off any leading '_' and whitespace, what's left is the variant.
variant = localeString.substring(endIndexOfCountryCode).stripLeading();
if (variant.startsWith("_")) {
variant = trimLeadingCharacter(variant, '_');
}
} }
String delimiter = "_";
if (variant.isEmpty() && country.startsWith("#")) { if (!localeString.contains("_") && localeString.contains(" ")) {
variant = country; delimiter = " ";
country = "";
} }
final String[] tokens = localeString.split(delimiter, -1);
return (language.length() > 0 ? new Locale(language, country, variant) : null); if (tokens.length == 1) {
final String language = tokens[0];
validateLocalePart(language);
return new Locale(language);
}
else if (tokens.length == 2) {
final String language = tokens[0];
validateLocalePart(language);
final String country = tokens[1];
validateLocalePart(country);
return new Locale(language, country);
}
else if (tokens.length > 2) {
final String language = tokens[0];
validateLocalePart(language);
final String country = tokens[1];
validateLocalePart(country);
final String variant = Arrays.stream(tokens).skip(2).collect(Collectors.joining(delimiter));
return new Locale(language, country, variant);
}
throw new IllegalArgumentException("Invalid locale format: '" + localeString + "'");
} }
private static void validateLocalePart(String localePart) { private static void validateLocalePart(String localePart) {

27
spring-core/src/test/java/org/springframework/util/StringUtilsTests.java

@ -624,12 +624,6 @@ class StringUtilsTests {
assertThat(locale).isEqualTo(expectedLocale); assertThat(locale).isEqualTo(expectedLocale);
} }
@Test
void parseLocaleStringWithMalformedLocaleString() {
Locale locale = StringUtils.parseLocaleString("_banjo_on_my_knee");
assertThat(locale).as("When given a malformed Locale string, must not return null.").isNotNull();
}
@Test @Test
void parseLocaleStringWithEmptyLocaleStringYieldsNullLocale() { void parseLocaleStringWithEmptyLocaleStringYieldsNullLocale() {
Locale locale = StringUtils.parseLocaleString(""); Locale locale = StringUtils.parseLocaleString("");
@ -668,22 +662,6 @@ class StringUtilsTests {
assertThat(locale.getVariant()).as("Multi-valued variant portion of the Locale not extracted correctly.").isEqualTo(variant); assertThat(locale.getVariant()).as("Multi-valued variant portion of the Locale not extracted correctly.").isEqualTo(variant);
} }
@Test // SPR-3671
void parseLocaleWithMultiValuedVariantUsingSpacesAsSeparatorsWithLotsOfLeadingWhitespace() {
String variant = "proper northern";
String localeString = "en GB " + variant; // lots of whitespace
Locale locale = StringUtils.parseLocaleString(localeString);
assertThat(locale.getVariant()).as("Multi-valued variant portion of the Locale not extracted correctly.").isEqualTo(variant);
}
@Test // SPR-3671
void parseLocaleWithMultiValuedVariantUsingUnderscoresAsSeparatorsWithLotsOfLeadingWhitespace() {
String variant = "proper_northern";
String localeString = "en_GB_____" + variant; // lots of underscores
Locale locale = StringUtils.parseLocaleString(localeString);
assertThat(locale.getVariant()).as("Multi-valued variant portion of the Locale not extracted correctly.").isEqualTo(variant);
}
@Test // SPR-7779 @Test // SPR-7779
void parseLocaleWithInvalidCharacters() { void parseLocaleWithInvalidCharacters() {
assertThatIllegalArgumentException().isThrownBy(() -> assertThatIllegalArgumentException().isThrownBy(() ->
@ -751,6 +729,11 @@ class StringUtilsTests {
assertThat(StringUtils.parseLocale("")).isNull(); assertThat(StringUtils.parseLocale("")).isNull();
} }
@Test
void parseLocaleStringWithEmptyCountryAndVariant() {
assertThat(StringUtils.parseLocale("be__TARASK").toString()).isEqualTo("be__TARASK");
}
@Test @Test
void split() { void split() {
assertThat(StringUtils.split("Hello, world", ",")).containsExactly("Hello", " world"); assertThat(StringUtils.split("Hello, world", ",")).containsExactly("Hello", " world");

Loading…
Cancel
Save