001/* 002 * Copyright (c) 2010-2024 Mark Allen, Norbert Bartels. 003 * 004 * Permission is hereby granted, free of charge, to any person obtaining a copy 005 * of this software and associated documentation files (the "Software"), to deal 006 * in the Software without restriction, including without limitation the rights 007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 008 * copies of the Software, and to permit persons to whom the Software is 009 * furnished to do so, subject to the following conditions: 010 * 011 * The above copyright notice and this permission notice shall be included in 012 * all copies or substantial portions of the Software. 013 * 014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 020 * THE SOFTWARE. 021 */ 022package com.restfb.util; 023 024import static java.lang.String.format; 025import static java.net.URLDecoder.decode; 026import static java.net.URLEncoder.encode; 027import static java.util.Collections.emptyMap; 028import static java.util.stream.Collectors.toList; 029 030import java.io.UnsupportedEncodingException; 031import java.nio.charset.StandardCharsets; 032import java.util.Arrays; 033import java.util.HashMap; 034import java.util.List; 035import java.util.Map; 036import java.util.regex.Pattern; 037import java.util.stream.Collectors; 038 039/** 040 * @author <a href="http://restfb.com">Mark Allen</a> 041 * @since 1.6.10 042 */ 043public final class UrlUtils { 044 045 /** 046 * Prevents instantiation. 047 */ 048 private UrlUtils() { 049 throw new IllegalStateException("UrlUtils must not be instantiated"); 050 } 051 052 /** 053 * URL-encodes a string. 054 * <p> 055 * Assumes {@code string} is in {@link StandardCharsets#UTF_8} format. 056 * 057 * @param string 058 * The string to URL-encode. 059 * @return The URL-encoded version of the input string, or {@code null} if {@code string} is {@code null}. 060 * @throws IllegalStateException 061 * If unable to URL-encode because the JVM doesn't support {@link StandardCharsets#UTF_8}. 062 */ 063 public static String urlEncode(String string) { 064 if (string == null) { 065 return null; 066 } 067 try { 068 return encode(string, StandardCharsets.UTF_8.name()); 069 } catch (UnsupportedEncodingException e) { 070 throw new IllegalStateException("Platform doesn't support " + StandardCharsets.UTF_8.name(), e); 071 } 072 } 073 074 /** 075 * URL-decodes a string. 076 * <p> 077 * Assumes {@code string} is in {@link StandardCharsets#UTF_8} format. 078 * 079 * @param string 080 * The string to URL-decode. 081 * @return The URL-decoded version of the input string, or {@code null} if {@code string} is {@code null}. 082 * @throws IllegalStateException 083 * If unable to URL-decode because the JVM doesn't support {@link StandardCharsets#UTF_8}. 084 * @since 1.6.5 085 */ 086 public static String urlDecode(String string) { 087 if (string == null) { 088 return null; 089 } 090 try { 091 return decode(string, StandardCharsets.UTF_8.name()); 092 } catch (UnsupportedEncodingException e) { 093 throw new IllegalStateException("Platform doesn't support " + StandardCharsets.UTF_8.name(), e); 094 } 095 } 096 097 /** 098 * For the given {@code queryString}, extract a mapping of query string parameter names to values. 099 * <p> 100 * Example of a {@code queryString} is {@code accessToken=123&expires=345}. 101 * 102 * @param queryString 103 * The URL query string from which parameters are extracted. 104 * @return A mapping of query string parameter names to values. If {@code queryString} is {@code null}, an empty 105 * {@code Map} is returned. 106 * @throws IllegalStateException 107 * If unable to URL-decode because the JVM doesn't support {@link StandardCharsets#UTF_8}. 108 */ 109 public static Map<String, List<String>> extractParametersFromQueryString(String queryString) { 110 if (queryString == null) { 111 return emptyMap(); 112 } 113 114 // If there is no ? character at the front of the string, append it. 115 return extractParametersFromUrl( 116 format("restfb://url%s", queryString.startsWith("?") ? queryString : "?" + queryString)); 117 } 118 119 /** 120 * For the given {@code url}, extract a mapping of query string parameter names to values. 121 * <p> 122 * Adapted from an implementation by BalusC and dfrankow, available at 123 * http://stackoverflow.com/questions/1667278/parsing-query-strings-in-java. 124 * 125 * @param url 126 * The URL from which parameters are extracted. 127 * @return A mapping of query string parameter names to values. If {@code url} is {@code null}, an empty {@code Map} 128 * is returned. 129 * @throws IllegalStateException 130 * If unable to URL-decode because the JVM doesn't support {@link StandardCharsets#UTF_8}. 131 */ 132 public static Map<String, List<String>> extractParametersFromUrl(String url) { 133 if (url == null) { 134 return emptyMap(); 135 } 136 137 Map<String, List<String>> parameters = new HashMap<>(); 138 String[] urlParts = url.split("\\?"); 139 140 if (urlParts.length > 1) { 141 String query = urlParts[1]; 142 parameters = Pattern.compile("&").splitAsStream(query) // 143 .map(s -> Arrays.copyOf(s.split("="), 2)) 144 .collect(Collectors.groupingBy(s -> urlDecode(s[0]), Collectors.mapping(s -> urlDecode(s[1]), toList()))); 145 } 146 147 return parameters; 148 } 149 150 /** 151 * Modify the query string in the given {@code url} and return the new url as String. 152 * <p> 153 * The given key/value pair is added to the url. If the key is already present, it is replaced with the new value. 154 * 155 * @param url 156 * The URL which parameters should be modified. 157 * @param key 158 * the key, that should be modified or added 159 * @param value 160 * the value of the key/value pair 161 * @return the modified URL as String 162 */ 163 public static String replaceOrAddQueryParameter(String url, String key, String value) { 164 String[] urlParts = url.split("\\?"); 165 String qParameter = key + "=" + value; 166 167 if (urlParts.length == 2) { 168 Map<String, List<String>> paramMap = extractParametersFromQueryString(urlParts[1]); 169 if (paramMap.containsKey(key)) { 170 String queryValue = paramMap.get(key).get(0); 171 return url.replace(key + "=" + queryValue, qParameter); 172 } else { 173 return url + "&" + qParameter; 174 } 175 176 } else { 177 return url + "?" + qParameter; 178 } 179 } 180 181 /** 182 * Remove the given key from the url query string and return the new URL as String. 183 * 184 * @param url 185 * The URL from which parameters are extracted. 186 * @param key 187 * the key, that should be removed 188 * @return the modified URL as String 189 */ 190 public static String removeQueryParameter(String url, String key) { 191 String[] urlParts = url.split("\\?"); 192 193 if (urlParts.length == 2) { 194 Map<String, List<String>> paramMap = extractParametersFromQueryString(urlParts[1]); 195 if (paramMap.containsKey(key)) { 196 String queryValue = paramMap.get(key).get(0); 197 String result = url.replace(key + "=" + queryValue, ""); 198 // improper separators have to be fixed 199 // @TODO find a better way to solve this 200 result = result.replace("?&", "?").replace("&&", "&"); 201 if (result.endsWith("&")) { 202 return result.substring(0, result.length() - 1); 203 } else { 204 return result; 205 } 206 } 207 } 208 return url; 209 } 210}