001/* 002 * (C) Copyright 2006-2017 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Anahide Tchertchian 018 * Florent Guillaume 019 * Kevin Leturc <[email protected]> 020 */ 021package org.nuxeo.common.utils; 022 023import static java.nio.charset.StandardCharsets.UTF_8; 024 025import java.io.UnsupportedEncodingException; 026import java.net.URI; 027import java.net.URISyntaxException; 028import java.net.URLDecoder; 029import java.net.URLEncoder; 030import java.util.ArrayList; 031import java.util.LinkedHashMap; 032import java.util.List; 033import java.util.Map; 034 035import org.apache.commons.logging.Log; 036import org.apache.commons.logging.LogFactory; 037 038/** 039 * Helper class to parse a URI or build one given parameters. 040 * 041 * @author Anahide Tchertchian 042 * @author Florent Guillaume 043 */ 044public final class URIUtils { 045 046 private static final Log log = LogFactory.getLog(URIUtils.class); 047 048 // This is an utility class. 049 private URIUtils() { 050 } 051 052 /** 053 * Creates an URI query given the request parameters. 054 * 055 * @return an URI query given the request parameters. 056 */ 057 public static String getURIQuery(Map<String, String> parameters) { 058 String query = null; 059 if (parameters != null) { 060 try { 061 List<String> items = new ArrayList<>(); 062 for (Map.Entry<String, String> paramInfo : parameters.entrySet()) { 063 String key = paramInfo.getKey(); 064 String value = paramInfo.getValue(); 065 if (key != null) { 066 if (value == null) { 067 value = ""; 068 } 069 items.add(String.format("%s=%s", URLEncoder.encode(key, UTF_8.name()), 070 URLEncoder.encode(value, UTF_8.name()))); 071 } 072 } 073 query = String.join("&", items); 074 } catch (UnsupportedEncodingException e) { 075 log.error("Failed to get uri query", e); 076 } 077 } 078 return query; 079 } 080 081 /** 082 * Returns an URI path given the uri. 083 */ 084 public static String getURIPath(String uri) { 085 if (uri == null) { 086 return null; 087 } 088 String path = uri; 089 int index = uri.indexOf('?'); 090 if (index != -1) { 091 path = uri.substring(0, index); 092 } 093 return path; 094 } 095 096 /** 097 * @return a map with request parameters information given an URI query. 098 */ 099 public static Map<String, String> getRequestParameters(String uriQuery) { 100 Map<String, String> parameters = null; 101 if (uriQuery != null && uriQuery.length() > 0) { 102 try { 103 String strippedQuery; 104 int index = uriQuery.indexOf('?'); 105 if (index != -1) { 106 strippedQuery = uriQuery.substring(index + 1); 107 } else { 108 strippedQuery = uriQuery; 109 } 110 String[] items = strippedQuery.split("&"); 111 if (items.length > 0) { 112 parameters = new LinkedHashMap<>(); 113 for (String item : items) { 114 String[] param = item.split("="); 115 if (param.length == 2) { 116 parameters.put(URLDecoder.decode(param[0], "UTF-8"), 117 URLDecoder.decode(param[1], "UTF-8")); 118 } else if (param.length == 1) { 119 parameters.put(URLDecoder.decode(param[0], "UTF-8"), null); 120 } 121 } 122 } 123 } catch (UnsupportedEncodingException e) { 124 log.error("Failed to get request parameters from uri", e); 125 } 126 } 127 return parameters; 128 } 129 130 public static String addParametersToURIQuery(String uriString, Map<String, String> parameters) { 131 if (uriString == null || parameters == null || parameters.isEmpty()) { 132 return uriString; 133 } 134 String uriPath = getURIPath(uriString); 135 Map<String, String> existingParams = getRequestParameters(uriString.substring(uriPath.length())); 136 if (existingParams == null) { 137 existingParams = new LinkedHashMap<>(); 138 } 139 existingParams.putAll(parameters); 140 String res; 141 if (!existingParams.isEmpty()) { 142 String newQuery = getURIQuery(existingParams); 143 res = uriPath + '?' + newQuery; 144 } else { 145 res = uriPath; 146 } 147 return res; 148 } 149 150 public static String quoteURIPathComponent(String s, boolean quoteSlash) { 151 return quoteURIPathComponent(s, quoteSlash, true); 152 } 153 154 /** 155 * Quotes a URI path component, with ability to quote "/" and "@" characters or not depending on the URI path 156 * 157 * @since 5.6 158 * @param s the uri path to quote 159 * @param quoteSlash true if "/" character should be quoted and replaced by %2F 160 * @param quoteAt true if "@" character should be quoted and replaced by %40 161 */ 162 public static String quoteURIPathComponent(String s, boolean quoteSlash, boolean quoteAt) { 163 return quoteURIPathComponent(s, quoteSlash ? "%2F" : null, quoteAt ? "%40" : null); 164 } 165 166 /** 167 * Quotes a URI path token. For example, a blob filename. It replaces "/" by "-". 168 * 169 * @since 7.3 170 * @param s the uri path token to quote 171 */ 172 public static String quoteURIPathToken(String s) { 173 return quoteURIPathComponent(s, "-", "%40"); 174 } 175 176 /** 177 * Quotes a URI path component, with ability to quote "/" and "@" characters or not depending on the URI path 178 * 179 * @since 5.6 180 * @param s the uri path to quote 181 * @param slashSequence if null, do not quote "/", otherwise, replace "/" by the given sequence 182 * @param atSequence if null, do not quote "@", otherwise, replace "@" by the given sequence 183 */ 184 protected static String quoteURIPathComponent(String s, String slashSequence, String atSequence) { 185 if ("".equals(s)) { 186 return s; 187 } 188 URI uri; 189 try { 190 // fake scheme so that a colon is not mistaken as a scheme 191 uri = new URI("x", s, null); 192 } catch (URISyntaxException e) { 193 throw new IllegalArgumentException("Illegal characters in: " + s, e); 194 } 195 String r = uri.toASCIIString().substring(2); 196 // replace reserved characters ;:$&+=?/[]@ 197 // FIXME: find a better way to do this... 198 r = r.replace(";", "%3B"); 199 r = r.replace(":", "%3A"); 200 r = r.replace("$", "%24"); 201 r = r.replace("&", "%26"); 202 r = r.replace("+", "%2B"); 203 r = r.replace("=", "%3D"); 204 r = r.replace("?", "%3F"); 205 r = r.replace("[", "%5B"); 206 r = r.replace("]", "%5D"); 207 if (atSequence != null) { 208 r = r.replace("@", atSequence); 209 } 210 if (slashSequence != null) { 211 r = r.replace("/", slashSequence); 212 } 213 return r; 214 } 215 216 public static String unquoteURIPathComponent(String s) { 217 if ("".equals(s)) { 218 return s; 219 } 220 URI uri; 221 try { 222 uri = new URI("http://x/" + s); 223 } catch (URISyntaxException e) { 224 throw new IllegalArgumentException("Illegal characters in: " + s, e); 225 } 226 String path = uri.getPath(); 227 if (path.startsWith("/") && !s.startsWith("/")) { 228 path = path.substring(1); 229 } 230 return path; 231 } 232 233}