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