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