001package com.nimbusds.common.util;
002
003
004import java.io.UnsupportedEncodingException;
005import java.net.MalformedURLException;
006import java.net.URL;
007import java.net.URLDecoder;
008import java.net.URLEncoder;
009import java.util.HashMap;
010import java.util.Map;
011
012
013/**
014 * URL operations.
015 */
016public class URLUtility {
017
018        
019        /**
020         * The default character set.
021         */
022        public static final String CHARSET = "utf-8";
023        
024        
025        /**
026         * Gets the base part (protocol, host, port and path) of the specified
027         * URL.
028         *
029         * @param url The URL. May be {@code null}.
030         *
031         * @return The base part of the URL, {@code null} if the original URL 
032         *         is {@code null} or doesn't specify a protocol.
033         */
034        public static URL getBaseURL(final URL url) {
035        
036                if (url == null)
037                        return null;
038                
039                try {
040                        return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getPath());
041                        
042                } catch (MalformedURLException e) {
043                
044                        return null;
045                }
046        }
047        
048        
049        /**
050         * Serialises the specified map of parameters into a URL query string. 
051         * The parameter keys and values are 
052         * {@code application/x-www-form-urlencoded} encoded.
053         *
054         * <p>Note that the '?' character preceding the query string in GET
055         * requests is not included in the returned string.
056         *
057         * <p>Example query string:
058         *
059         * <pre>
060         * response_type=code
061         * &amp;client_id=s6BhdRkqt3
062         * &amp;state=xyz
063         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
064         * </pre>
065         *
066         * <p>The opposite method is {@link #parseParameters}.
067         *
068         * @param params A map of the URL query parameters. May be empty or
069         *               {@code null}.
070         *
071         * @return The serialised URL query string, empty if no parameters.
072         */
073        public static String serializeParameters(final Map<String,String> params) {
074        
075                if (params == null || params.isEmpty())
076                        return "";
077                
078                var sb = new StringBuilder();
079
080                for (Map.Entry<String, String> entry : params.entrySet()) {
081
082                        if (entry.getKey() == null || entry.getValue() == null)
083                                continue;
084
085                        try {
086                                String encodedKey = URLEncoder.encode(entry.getKey(), CHARSET);
087                                String encodedValue = URLEncoder.encode(entry.getValue(), CHARSET);
088
089                                if (!sb.isEmpty())
090                                        sb.append('&');
091
092                                sb.append(encodedKey);
093                                sb.append('=');
094                                sb.append(encodedValue);
095
096                        } catch (UnsupportedEncodingException e) {
097
098                                // UTF-8 should always be supported
099                        }
100                }
101                
102                return sb.toString();
103        }
104
105
106        /**
107         * Parses the specified URL query string into a parameter map. If a 
108         * parameter has multiple values only the first one will be saved. The
109         * parameter keys and values are 
110         * {@code application/x-www-form-urlencoded} decoded.
111         *
112         * <p>Note that the '?' character preceding the query string in GET
113         * requests must not be included.
114         *
115         * <p>Example query string:
116         *
117         * <pre>
118         * response_type=code
119         * &amp;client_id=s6BhdRkqt3
120         * &amp;state=xyz
121         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
122         * </pre>
123         *
124         * <p>The opposite method {@link #serializeParameters}.
125         *
126         * @param query The URL query string to parse. May be {@code null}.
127         *
128         * @return A map of the URL query parameters, empty if none are found.
129         */
130        public static Map<String,String> parseParameters(final String query) {
131                
132                Map<String,String> params = new HashMap<>();
133                
134                if (query == null)
135                        return params; // empty map
136                
137                try {
138                        for (String param : query.split("&")) {
139
140                                String[] pair = param.split("=");
141
142                                String key = URLDecoder.decode(pair[0], CHARSET);
143                                
144                                // Save the first value only
145                                if (params.containsKey(key))
146                                        continue;
147
148                                String value = "";
149
150                                if (pair.length > 1)
151                                        value = URLDecoder.decode(pair[1], CHARSET);
152                                
153                                params.put(key, value);
154                        }
155                        
156                } catch (UnsupportedEncodingException e) {
157                        
158                        // UTF-8 should always be supported
159                }
160                
161                return params;
162        }
163
164
165        /**
166         * Prevents public instantiation.
167         */
168        private URLUtility() {
169        
170                // do nothing
171        }
172}