diff --git a/common/src/main/java/com/microsoft/identity/common/internal/util/QueryParamsAdapter.java b/common/src/main/java/com/microsoft/identity/common/internal/util/QueryParamsAdapter.java index 121e176f35..4ad0392339 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/util/QueryParamsAdapter.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/util/QueryParamsAdapter.java @@ -23,13 +23,17 @@ package com.microsoft.identity.common.internal.util; import android.text.TextUtils; +import android.util.Pair; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; import com.google.gson.TypeAdapter; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +import com.microsoft.identity.common.java.exception.ClientException; +import com.microsoft.identity.common.logging.Logger; import java.io.IOException; import java.lang.reflect.Type; @@ -40,9 +44,14 @@ /** * Class to serialize and deserialize query parameters from List> to json String - * and vice versa + * and vice versa. + * + * NOTE: Even we no longer use Pair (Since it's android-only), we are keeping this the same + * to maintain backcompat with serialized value from older common that still uses it. */ -public class QueryParamsAdapter extends TypeAdapter>> { +public class QueryParamsAdapter extends TypeAdapter>> { + + private static final String TAG = QueryParamsAdapter.class.getSimpleName(); private static final Gson mGson; @@ -56,24 +65,25 @@ public class QueryParamsAdapter extends TypeAdapter> queryParams) throws IOException { + public void write(final JsonWriter out, final List> queryParams) throws IOException { out.beginObject(); - for (final Map.Entry keyValuePair : queryParams) { - out.name(keyValuePair.getKey()); - out.value(keyValuePair.getValue()); + for (final Pair keyValuePair : queryParams) { + out.name(keyValuePair.first); + out.value(keyValuePair.second); } + out.endObject(); } @Override - public List> read(final JsonReader in) throws IOException { + public List> read(final JsonReader in) throws IOException { in.beginObject(); - final List> result = new ArrayList<>(); + final List> result = new ArrayList<>(); while (in.hasNext()) { final String key = in.nextName(); final String value = in.nextString(); - final Map.Entry keyValuePair = new AbstractMap.SimpleEntry<>(key, value); + final Pair keyValuePair = new Pair<>(key, value); result.add(keyValuePair); } in.endObject(); @@ -81,22 +91,43 @@ public List> read(final JsonReader in) throws IOExcept } public static String _toJson(final List> extraQueryStringParameters) { - return mGson.toJson(extraQueryStringParameters, getListType()); + final List> extraQpPairs = new ArrayList<>(); + for (final Map.Entry entry: extraQueryStringParameters) { + extraQpPairs.add(new Pair(entry.getKey(), entry.getValue())); + } + return mGson.toJson(extraQpPairs, getPairListType()); } - public static List> _fromJson(final String jsonString) { + public static List> _fromJson(final String jsonString) + throws ClientException{ + final String methodName = ":_fromJson"; + if (TextUtils.isEmpty(jsonString)) { return new ArrayList<>(); } - return mGson.fromJson(jsonString, getListType()); + + try { + final List> extraQpPairs = mGson.fromJson(jsonString, getPairListType()); + final List> extraQpMapEntries = new ArrayList<>(); + for (final Pair entry: extraQpPairs) { + if (!StringUtil.isEmpty(entry.first)) { + extraQpMapEntries.add(new AbstractMap.SimpleEntry(entry.first, entry.second)); + } + } + return extraQpMapEntries; + } catch (final JsonSyntaxException e) { + final String errorMessage = "malformed json string:" + jsonString; + Logger.error(TAG + methodName, errorMessage, e); + throw new ClientException(ClientException.JSON_PARSE_FAILURE, errorMessage, e); + } } /** * Create a Type for the List of query params * - * @return a Type object representing the type of the query params in this case List> + * @return a Type object representing the type of the query params in this case List> */ - private static Type getListType() { - return TypeToken.getParameterized(List.class, TypeToken.getParameterized(Map.Entry.class, String.class, String.class).getRawType()).getType(); + private static Type getPairListType() { + return TypeToken.getParameterized(List.class, TypeToken.getParameterized(Pair.class, String.class, String.class).getRawType()).getType(); } } diff --git a/common/src/test/java/com/microsoft/identity/common/internal/util/QueryParamsAdapterTest.java b/common/src/test/java/com/microsoft/identity/common/internal/util/QueryParamsAdapterTest.java new file mode 100644 index 0000000000..24cfb86d36 --- /dev/null +++ b/common/src/test/java/com/microsoft/identity/common/internal/util/QueryParamsAdapterTest.java @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package com.microsoft.identity.common.internal.util; + +import com.microsoft.identity.common.java.exception.ClientException; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@RunWith(RobolectricTestRunner.class) +public class QueryParamsAdapterTest { + + @Test + public void testConvertFromNullJsonString() throws Exception { + final List> result = QueryParamsAdapter._fromJson(null); + Assert.assertEquals(0, result.size()); + } + + @Test + public void testConvertFromEmptyJsonString() throws Exception { + final List> result = QueryParamsAdapter._fromJson(""); + Assert.assertEquals(0, result.size()); + } + + @Test + public void testConvertToJson() throws Exception { + final List> input = new ArrayList<>(); + input.add(new AbstractMap.SimpleEntry("eqp1", "1")); + input.add(new AbstractMap.SimpleEntry("eqp2", "2")); + + final String expected = "[{\"first\":\"eqp1\",\"second\":\"1\"},{\"first\":\"eqp2\",\"second\":\"2\"}]"; + + Assert.assertEquals(expected, QueryParamsAdapter._toJson(input)); + } + + @Test + public void testConvertFromJson() throws Exception { + final String input = "[{\"first\":\"eqp1\",\"second\":\"1\"},{\"first\":\"eqp2\",\"second\":\"2\"}]"; + + final List> expected = new ArrayList<>(); + expected.add(new AbstractMap.SimpleEntry("eqp1", "1")); + expected.add(new AbstractMap.SimpleEntry("eqp2", "2")); + + Assert.assertEquals(expected, QueryParamsAdapter._fromJson(input)); + } + + @Test + public void testConvertFromIncorrectJson() throws Exception { + // This is what we get if we serialized List directly. + final String input = "[{\"key\":\"eqp1\",\"value\":\"1\"},{\"key\":\"eqp2\",\"value\":\"2\"}]"; + Assert.assertEquals(0, QueryParamsAdapter._fromJson(input).size()); + } + + @Test + public void testConvertFromMalformedJson(){ + final String input = "[{\"eqp1\", \"1\"}, {\"eqp2\", \"2\"}]"; + try { + QueryParamsAdapter._fromJson(input); + Assert.fail(); + } catch (final ClientException e){ + Assert.assertEquals(ClientException.JSON_PARSE_FAILURE, e.getErrorCode()); + } + } + + @Test + public void testConvertFromTruncatedJson(){ + final String input = "[{\"key1\""; + try { + QueryParamsAdapter._fromJson(input); + Assert.fail(); + } catch (final ClientException e){ + Assert.assertEquals(ClientException.JSON_PARSE_FAILURE, e.getErrorCode()); + } + } +} \ No newline at end of file