001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk.util; 019 020 021import java.io.UnsupportedEncodingException; 022import java.net.MalformedURLException; 023import java.net.URL; 024import java.net.URLDecoder; 025import java.net.URLEncoder; 026import java.util.HashMap; 027import java.util.Map; 028import java.util.StringTokenizer; 029 030 031/** 032 * URL operations. 033 */ 034public class URLUtils { 035 036 037 /** 038 * The default UTF-8 character set. 039 */ 040 public static final String CHARSET = "utf-8"; 041 042 043 /** 044 * Gets the base part (protocol, host, port and path) of the specified 045 * URL. 046 * 047 * @param url The URL. May be {@code null}. 048 * 049 * @return The base part of the URL, {@code null} if the original URL 050 * is {@code null} or doesn't specify a protocol. 051 */ 052 public static URL getBaseURL(final URL url) { 053 054 if (url == null) 055 return null; 056 057 try { 058 return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getPath()); 059 060 } catch (MalformedURLException e) { 061 062 return null; 063 } 064 } 065 066 067 /** 068 * Serialises the specified map of parameters into a URL query string. 069 * The parameter keys and values are 070 * {@code application/x-www-form-urlencoded} encoded. 071 * 072 * <p>Note that the '?' character preceding the query string in GET 073 * requests is not included in the returned string. 074 * 075 * <p>Example query string: 076 * 077 * <pre> 078 * response_type=code 079 * &client_id=s6BhdRkqt3 080 * &state=xyz 081 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 082 * </pre> 083 * 084 * <p>The opposite method is {@link #parseParameters}. 085 * 086 * @param params A map of the URL query parameters. May be empty or 087 * {@code null}. 088 * 089 * @return The serialised URL query string, empty if no parameters. 090 */ 091 public static String serializeParameters(final Map<String,String> params) { 092 093 if (params == null || params.isEmpty()) 094 return ""; 095 096 StringBuilder sb = new StringBuilder(); 097 098 for (Map.Entry<String,String> entry: params.entrySet()) { 099 100 if (entry.getKey() == null) 101 continue; 102 103 String value = entry.getValue() != null ? entry.getValue() : ""; 104 105 try { 106 String encodedKey = URLEncoder.encode(entry.getKey(), CHARSET); 107 String encodedValue = URLEncoder.encode(value, CHARSET); 108 109 if (sb.length() > 0) 110 sb.append('&'); 111 112 sb.append(encodedKey); 113 sb.append('='); 114 sb.append(encodedValue); 115 116 } catch (UnsupportedEncodingException e) { 117 118 // UTF-8 should always be supported 119 throw new RuntimeException(e.getMessage(), e); 120 } 121 } 122 123 return sb.toString(); 124 } 125 126 127 /** 128 * Serialises the specified map of parameters into a URL query string. 129 * Supports multiple key / value pairs that have the same key. The 130 * parameter keys and values are 131 * {@code application/x-www-form-urlencoded} encoded. 132 * 133 * <p>Note that the '?' character preceding the query string in GET 134 * requests is not included in the returned string. 135 * 136 * <p>Example query string: 137 * 138 * <pre> 139 * response_type=code 140 * &client_id=s6BhdRkqt3 141 * &state=xyz 142 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 143 * </pre> 144 * 145 * <p>The opposite method is {@link #parseParameters}. 146 * 147 * @param params A map of the URL query parameters. May be empty or 148 * {@code null}. 149 * 150 * @return The serialised URL query string, empty if no parameters. 151 */ 152 public static String serializeParametersAlt(final Map<String,String[]> params) { 153 154 if (params == null || params.isEmpty()) 155 return ""; 156 157 StringBuilder sb = new StringBuilder(); 158 159 for (Map.Entry<String,String[]> entry: params.entrySet()) { 160 161 if (entry.getKey() == null || entry.getValue() == null) 162 continue; 163 164 for (String value: entry.getValue()) { 165 166 if (value == null) 167 value = ""; 168 169 try { 170 String encodedKey = URLEncoder.encode(entry.getKey(), CHARSET); 171 String encodedValue = URLEncoder.encode(value, CHARSET); 172 173 if (sb.length() > 0) 174 sb.append('&'); 175 176 sb.append(encodedKey); 177 sb.append('='); 178 sb.append(encodedValue); 179 180 } catch (UnsupportedEncodingException e) { 181 182 // UTF-8 should always be supported 183 throw new RuntimeException(e.getMessage(), e); 184 } 185 } 186 } 187 188 return sb.toString(); 189 } 190 191 192 /** 193 * Parses the specified URL query string into a parameter map. If a 194 * parameter has multiple values only the first one will be saved. The 195 * parameter keys and values are 196 * {@code application/x-www-form-urlencoded} decoded. 197 * 198 * <p>Note that the '?' character preceding the query string in GET 199 * requests must not be included. 200 * 201 * <p>Example query string: 202 * 203 * <pre> 204 * response_type=code 205 * &client_id=s6BhdRkqt3 206 * &state=xyz 207 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 208 * </pre> 209 * 210 * <p>The opposite method {@link #serializeParameters}. 211 * 212 * @param query The URL query string to parse. May be {@code null}. 213 * 214 * @return A map of the URL query parameters, empty if none are found. 215 */ 216 public static Map<String,String> parseParameters(final String query) { 217 218 Map<String,String> params = new HashMap<>(); 219 220 if (StringUtils.isBlank(query)) { 221 return params; // empty map 222 } 223 224 try { 225 StringTokenizer st = new StringTokenizer(query.trim(), "&"); 226 227 while(st.hasMoreTokens()) { 228 229 String param = st.nextToken(); 230 231 String pair[] = param.split("=", 2); // Split around the first '=', see issue #169 232 233 String key = URLDecoder.decode(pair[0], CHARSET); 234 235 // Save the first value only 236 if (params.containsKey(key)) 237 continue; 238 239 String value = ""; 240 241 if (pair.length > 1) { 242 value = URLDecoder.decode(pair[1], CHARSET); 243 } 244 245 params.put(key, value); 246 } 247 248 } catch (UnsupportedEncodingException e) { 249 250 // UTF-8 should always be supported 251 } 252 253 return params; 254 } 255 256 257 /** 258 * Prevents instantiation. 259 */ 260 private URLUtils() { 261 262 // do nothing 263 } 264}