001/*
002 * Copyright 2015 The AppAuth for Android Authors. All Rights Reserved.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the
010 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
011 * express or implied. See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014
015package net.openid.appauth.internal;
016
017import android.net.Uri;
018import android.os.Bundle;
019import android.text.TextUtils;
020import androidx.annotation.NonNull;
021import androidx.annotation.Nullable;
022import androidx.browser.customtabs.CustomTabsService;
023import androidx.core.util.Pair;
024
025import net.openid.appauth.Preconditions;
026
027import java.io.UnsupportedEncodingException;
028import java.net.URLDecoder;
029import java.net.URLEncoder;
030import java.util.ArrayList;
031import java.util.Collections;
032import java.util.HashMap;
033import java.util.List;
034import java.util.Map;
035
036/**
037 * Utility methods for extracting parameters from Uri objects.
038 */
039public final class UriUtil {
040
041    private UriUtil() {
042        throw new IllegalStateException("This type is not intended to be instantiated");
043    }
044
045    public static Uri parseUriIfAvailable(@Nullable String uri) {
046        if (uri == null) {
047            return null;
048        }
049
050        return Uri.parse(uri);
051    }
052
053    public static void appendQueryParameterIfNotNull(
054            @NonNull Uri.Builder uriBuilder,
055            @NonNull String paramName,
056            @Nullable Object value) {
057        if (value == null) {
058            return;
059        }
060
061        String valueStr = value.toString();
062        if (valueStr == null) {
063            return;
064        }
065
066        uriBuilder.appendQueryParameter(paramName, value.toString());
067    }
068
069    public static Long getLongQueryParameter(@NonNull Uri uri, @NonNull String param) {
070        String valueStr = uri.getQueryParameter(param);
071        if (valueStr != null) {
072            return Long.parseLong(valueStr);
073        }
074        return null;
075    }
076
077    public static List<Bundle> toCustomTabUriBundle(Uri[] uris, int startIndex) {
078        Preconditions.checkArgument(startIndex >= 0, "startIndex must be positive");
079        if (uris == null || uris.length <= startIndex) {
080            return Collections.emptyList();
081        }
082
083        List<Bundle> uriBundles = new ArrayList<>(uris.length - startIndex);
084        for (int i = startIndex; i < uris.length; i++) {
085            if (uris[i] == null) {
086                Logger.warn("Null URI in possibleUris list - ignoring");
087                continue;
088            }
089
090            Bundle uriBundle = new Bundle();
091            uriBundle.putParcelable(CustomTabsService.KEY_URL, uris[i]);
092            uriBundles.add(uriBundle);
093        }
094
095        return uriBundles;
096    }
097
098    @NonNull
099    public static String formUrlEncode(@Nullable Map<String, String> parameters) {
100        if (parameters == null) {
101            return "";
102        }
103
104        List<String> queryParts = new ArrayList<>();
105        for (Map.Entry<String, String> param : parameters.entrySet()) {
106            queryParts.add(param.getKey() + "=" + formUrlEncodeValue(param.getValue()));
107        }
108        return TextUtils.join("&", queryParts);
109    }
110
111    @NonNull
112    public static String formUrlEncodeValue(@NonNull String value) {
113        Preconditions.checkNotNull(value);
114        try {
115            return URLEncoder.encode(value, "utf-8");
116        } catch (UnsupportedEncodingException ex) {
117            // utf-8 should always be supported
118            throw new IllegalStateException("Unable to encode using UTF-8");
119        }
120    }
121
122    public static List<Pair<String, String>> formUrlDecode(String encoded) {
123        if (TextUtils.isEmpty(encoded)) {
124            return Collections.emptyList();
125        }
126
127        String[] parts = encoded.split("&");
128        List<Pair<String, String>> params = new ArrayList<>();
129
130        for (String part : parts) {
131            String[] paramAndValue = part.split("=");
132            String param = paramAndValue[0];
133            String encodedValue = paramAndValue[1];
134
135            try {
136                params.add(Pair.create(param, URLDecoder.decode(encodedValue, "utf-8")));
137            } catch (UnsupportedEncodingException ex) {
138                Logger.error("Unable to decode parameter, ignoring", ex);
139            }
140        }
141
142        return params;
143    }
144
145    public static Map<String, String> formUrlDecodeUnique(String encoded) {
146        List<Pair<String, String>> params = UriUtil.formUrlDecode(encoded);
147        Map<String, String> uniqueParams = new HashMap<>();
148
149        for (Pair<String, String> param : params) {
150            uniqueParams.put(param.first, param.second);
151        }
152
153        return uniqueParams;
154    }
155}