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 * &client_id=s6BhdRkqt3 065 * &state=xyz 066 * &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) 086 continue; 087 088 String value = entry.getValue() != null ? entry.getValue() : ""; 089 090 try { 091 String encodedKey = URLEncoder.encode(entry.getKey(), CHARSET); 092 String encodedValue = URLEncoder.encode(value, CHARSET); 093 094 if (sb.length() > 0) 095 sb.append('&'); 096 097 sb.append(encodedKey); 098 sb.append('='); 099 sb.append(encodedValue); 100 101 } catch (UnsupportedEncodingException e) { 102 103 // UTF-8 should always be supported 104 throw new RuntimeException(e.getMessage(), e); 105 } 106 } 107 108 return sb.toString(); 109 } 110 111 112 /** 113 * Serialises the specified map of parameters into a URL query string. 114 * Supports multiple key / value pairs that have the same key. The 115 * parameter keys and values are 116 * {@code application/x-www-form-urlencoded} encoded. 117 * 118 * <p>Note that the '?' character preceding the query string in GET 119 * requests is not included in the returned string. 120 * 121 * <p>Example query string: 122 * 123 * <pre> 124 * response_type=code 125 * &client_id=s6BhdRkqt3 126 * &state=xyz 127 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 128 * </pre> 129 * 130 * <p>The opposite method is {@link #parseParameters}. 131 * 132 * @param params A map of the URL query parameters. May be empty or 133 * {@code null}. 134 * 135 * @return The serialised URL query string, empty if no parameters. 136 */ 137 public static String serializeParametersAlt(final Map<String,String[]> params) { 138 139 if (params == null || params.isEmpty()) 140 return ""; 141 142 StringBuilder sb = new StringBuilder(); 143 144 for (Map.Entry<String,String[]> entry: params.entrySet()) { 145 146 if (entry.getKey() == null || entry.getValue() == null) 147 continue; 148 149 for (String value: entry.getValue()) { 150 151 if (value == null) 152 value = ""; 153 154 try { 155 String encodedKey = URLEncoder.encode(entry.getKey(), CHARSET); 156 String encodedValue = URLEncoder.encode(value, CHARSET); 157 158 if (sb.length() > 0) 159 sb.append('&'); 160 161 sb.append(encodedKey); 162 sb.append('='); 163 sb.append(encodedValue); 164 165 } catch (UnsupportedEncodingException e) { 166 167 // UTF-8 should always be supported 168 throw new RuntimeException(e.getMessage(), e); 169 } 170 } 171 } 172 173 return sb.toString(); 174 } 175 176 177 /** 178 * Parses the specified URL query string into a parameter map. If a 179 * parameter has multiple values only the first one will be saved. The 180 * parameter keys and values are 181 * {@code application/x-www-form-urlencoded} decoded. 182 * 183 * <p>Note that the '?' character preceding the query string in GET 184 * requests must not be included. 185 * 186 * <p>Example query string: 187 * 188 * <pre> 189 * response_type=code 190 * &client_id=s6BhdRkqt3 191 * &state=xyz 192 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 193 * </pre> 194 * 195 * <p>The opposite method {@link #serializeParameters}. 196 * 197 * @param query The URL query string to parse. May be {@code null}. 198 * 199 * @return A map of the URL query parameters, empty if none are found. 200 */ 201 public static Map<String,String> parseParameters(final String query) { 202 203 Map<String,String> params = new HashMap<>(); 204 205 if (StringUtils.isBlank(query)) { 206 return params; // empty map 207 } 208 209 try { 210 StringTokenizer st = new StringTokenizer(query.trim(), "&"); 211 212 while(st.hasMoreTokens()) { 213 214 String param = st.nextToken(); 215 216 String pair[] = param.split("=", 2); // Split around the first '=', see issue #169 217 218 String key = URLDecoder.decode(pair[0], CHARSET); 219 220 // Save the first value only 221 if (params.containsKey(key)) 222 continue; 223 224 String value = ""; 225 226 if (pair.length > 1) { 227 value = URLDecoder.decode(pair[1], CHARSET); 228 } 229 230 params.put(key, value); 231 } 232 233 } catch (UnsupportedEncodingException e) { 234 235 // UTF-8 should always be supported 236 } 237 238 return params; 239 } 240 241 242 /** 243 * Prevents instantiation. 244 */ 245 private URLUtils() { 246 247 // do nothing 248 } 249}