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