From 2786f83291c9d481750c874316f3588e9e3537ff Mon Sep 17 00:00:00 2001 From: Colin Decker Date: Mon, 7 Aug 2023 09:15:17 -0700 Subject: [PATCH] Change `MediaType.parse` to allow and skip over linear whitespace on either side of the `=` separator in a parameter (`attribute=value`) as well as on either side of the `/` between the type and subtype (because nothing in the specification suggests that it be treated differently than the `;` or the `=`). [RFC 822](https://datatracker.ietf.org/doc/html/rfc822), which specifies the notation used to describe the syntax of a media type in [RFC 2045](https://datatracker.ietf.org/doc/html/rfc2045#section-5.1), seems to [indicate](https://datatracker.ietf.org/doc/html/rfc822#section-3.1.4) that spaces should be allowed here (and in fact if it didn't, nothing in the specification would allow them around the `;` either). Fixes https://github.com/google/guava/issues/6663. RELNOTES=`net`: Made `MediaType.parse` allow and skip over whitespace around the `/` and `=` separator tokens in addition to the `;` separator for which it was already being allowed. PiperOrigin-RevId: 554496121 --- .../com/google/common/net/MediaTypeTest.java | 21 ++++++++++++++++--- .../src/com/google/common/net/MediaType.java | 14 ++++++++----- .../com/google/common/net/MediaTypeTest.java | 21 ++++++++++++++++--- .../src/com/google/common/net/MediaType.java | 14 ++++++++----- 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/android/guava-tests/test/com/google/common/net/MediaTypeTest.java b/android/guava-tests/test/com/google/common/net/MediaTypeTest.java index 574db68e80b3..a3428b32f53d 100644 --- a/android/guava-tests/test/com/google/common/net/MediaTypeTest.java +++ b/android/guava-tests/test/com/google/common/net/MediaTypeTest.java @@ -16,8 +16,6 @@ package com.google.common.net; -import static com.google.common.base.Charsets.UTF_16; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.net.MediaType.ANY_APPLICATION_TYPE; import static com.google.common.net.MediaType.ANY_AUDIO_TYPE; import static com.google.common.net.MediaType.ANY_IMAGE_TYPE; @@ -31,6 +29,8 @@ import static java.lang.reflect.Modifier.isFinal; import static java.lang.reflect.Modifier.isPublic; import static java.lang.reflect.Modifier.isStatic; +import static java.nio.charset.StandardCharsets.UTF_16; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; @@ -512,6 +512,14 @@ public void testParse_badInput() { } } + // https://github.com/google/guava/issues/6663 + public void testParse_spaceInParameterSeparator() { + assertThat(MediaType.parse("text/plain; charset =utf-8").charset()).hasValue(UTF_8); + assertThat(MediaType.parse("text/plain; charset= utf-8").charset()).hasValue(UTF_8); + assertThat(MediaType.parse("text/plain; charset = utf-8").charset()).hasValue(UTF_8); + assertThat(MediaType.parse("text/plain;charset =utf-8").charset()).hasValue(UTF_8); + } + public void testGetCharset() { assertThat(MediaType.parse("text/plain").charset()).isAbsent(); assertThat(MediaType.parse("text/plain; charset=utf-8").charset()).hasValue(UTF_8); @@ -556,6 +564,9 @@ public void testEquals() { MediaType.create("TEXT", "PLAIN"), MediaType.parse("text/plain"), MediaType.parse("TEXT/PLAIN"), + MediaType.parse("text /plain"), + MediaType.parse("TEXT/ plain"), + MediaType.parse("text / plain"), MediaType.create("text", "plain").withParameter("a", "1").withoutParameters()) .addEqualityGroup( MediaType.create("text", "plain").withCharset(UTF_8), @@ -571,7 +582,11 @@ public void testEquals() { MediaType.parse("text/plain; charset=\"utf-8\""), MediaType.parse("text/plain; charset=\"\\u\\tf-\\8\""), MediaType.parse("text/plain; charset=UTF-8"), - MediaType.parse("text/plain ; charset=utf-8")) + MediaType.parse("text/plain ; charset=utf-8"), + MediaType.parse("text/plain; charset =UTF-8"), + MediaType.parse("text/plain; charset= UTF-8"), + MediaType.parse("text/plain; charset = UTF-8"), + MediaType.parse("text/plain; charset=\tUTF-8")) .addEqualityGroup(MediaType.parse("text/plain; charset=utf-8; charset=utf-8")) .addEqualityGroup( MediaType.create("text", "plain").withParameter("a", "value"), diff --git a/android/guava/src/com/google/common/net/MediaType.java b/android/guava/src/com/google/common/net/MediaType.java index c7da5349198e..99d47dca70c7 100644 --- a/android/guava/src/com/google/common/net/MediaType.java +++ b/android/guava/src/com/google/common/net/MediaType.java @@ -1052,15 +1052,13 @@ public static MediaType parse(String input) { Tokenizer tokenizer = new Tokenizer(input); try { String type = tokenizer.consumeToken(TOKEN_MATCHER); - tokenizer.consumeCharacter('/'); + consumeSeparator(tokenizer, '/'); String subtype = tokenizer.consumeToken(TOKEN_MATCHER); ImmutableListMultimap.Builder parameters = ImmutableListMultimap.builder(); while (tokenizer.hasMore()) { - tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); - tokenizer.consumeCharacter(';'); - tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); + consumeSeparator(tokenizer, ';'); String attribute = tokenizer.consumeToken(TOKEN_MATCHER); - tokenizer.consumeCharacter('='); + consumeSeparator(tokenizer, '='); String value; if ('"' == tokenizer.previewChar()) { tokenizer.consumeCharacter('"'); @@ -1086,6 +1084,12 @@ public static MediaType parse(String input) { } } + private static void consumeSeparator(Tokenizer tokenizer, char c) { + tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); + tokenizer.consumeCharacter(c); + tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); + } + private static final class Tokenizer { final String input; int position = 0; diff --git a/guava-tests/test/com/google/common/net/MediaTypeTest.java b/guava-tests/test/com/google/common/net/MediaTypeTest.java index 574db68e80b3..a3428b32f53d 100644 --- a/guava-tests/test/com/google/common/net/MediaTypeTest.java +++ b/guava-tests/test/com/google/common/net/MediaTypeTest.java @@ -16,8 +16,6 @@ package com.google.common.net; -import static com.google.common.base.Charsets.UTF_16; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.net.MediaType.ANY_APPLICATION_TYPE; import static com.google.common.net.MediaType.ANY_AUDIO_TYPE; import static com.google.common.net.MediaType.ANY_IMAGE_TYPE; @@ -31,6 +29,8 @@ import static java.lang.reflect.Modifier.isFinal; import static java.lang.reflect.Modifier.isPublic; import static java.lang.reflect.Modifier.isStatic; +import static java.nio.charset.StandardCharsets.UTF_16; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; @@ -512,6 +512,14 @@ public void testParse_badInput() { } } + // https://github.com/google/guava/issues/6663 + public void testParse_spaceInParameterSeparator() { + assertThat(MediaType.parse("text/plain; charset =utf-8").charset()).hasValue(UTF_8); + assertThat(MediaType.parse("text/plain; charset= utf-8").charset()).hasValue(UTF_8); + assertThat(MediaType.parse("text/plain; charset = utf-8").charset()).hasValue(UTF_8); + assertThat(MediaType.parse("text/plain;charset =utf-8").charset()).hasValue(UTF_8); + } + public void testGetCharset() { assertThat(MediaType.parse("text/plain").charset()).isAbsent(); assertThat(MediaType.parse("text/plain; charset=utf-8").charset()).hasValue(UTF_8); @@ -556,6 +564,9 @@ public void testEquals() { MediaType.create("TEXT", "PLAIN"), MediaType.parse("text/plain"), MediaType.parse("TEXT/PLAIN"), + MediaType.parse("text /plain"), + MediaType.parse("TEXT/ plain"), + MediaType.parse("text / plain"), MediaType.create("text", "plain").withParameter("a", "1").withoutParameters()) .addEqualityGroup( MediaType.create("text", "plain").withCharset(UTF_8), @@ -571,7 +582,11 @@ public void testEquals() { MediaType.parse("text/plain; charset=\"utf-8\""), MediaType.parse("text/plain; charset=\"\\u\\tf-\\8\""), MediaType.parse("text/plain; charset=UTF-8"), - MediaType.parse("text/plain ; charset=utf-8")) + MediaType.parse("text/plain ; charset=utf-8"), + MediaType.parse("text/plain; charset =UTF-8"), + MediaType.parse("text/plain; charset= UTF-8"), + MediaType.parse("text/plain; charset = UTF-8"), + MediaType.parse("text/plain; charset=\tUTF-8")) .addEqualityGroup(MediaType.parse("text/plain; charset=utf-8; charset=utf-8")) .addEqualityGroup( MediaType.create("text", "plain").withParameter("a", "value"), diff --git a/guava/src/com/google/common/net/MediaType.java b/guava/src/com/google/common/net/MediaType.java index c7da5349198e..99d47dca70c7 100644 --- a/guava/src/com/google/common/net/MediaType.java +++ b/guava/src/com/google/common/net/MediaType.java @@ -1052,15 +1052,13 @@ public static MediaType parse(String input) { Tokenizer tokenizer = new Tokenizer(input); try { String type = tokenizer.consumeToken(TOKEN_MATCHER); - tokenizer.consumeCharacter('/'); + consumeSeparator(tokenizer, '/'); String subtype = tokenizer.consumeToken(TOKEN_MATCHER); ImmutableListMultimap.Builder parameters = ImmutableListMultimap.builder(); while (tokenizer.hasMore()) { - tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); - tokenizer.consumeCharacter(';'); - tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); + consumeSeparator(tokenizer, ';'); String attribute = tokenizer.consumeToken(TOKEN_MATCHER); - tokenizer.consumeCharacter('='); + consumeSeparator(tokenizer, '='); String value; if ('"' == tokenizer.previewChar()) { tokenizer.consumeCharacter('"'); @@ -1086,6 +1084,12 @@ public static MediaType parse(String input) { } } + private static void consumeSeparator(Tokenizer tokenizer, char c) { + tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); + tokenizer.consumeCharacter(c); + tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); + } + private static final class Tokenizer { final String input; int position = 0;