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