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