001/* 002 * Copyright 2002-2017 the original author or authors. 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 * https://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 017package org.springframework.web.util; 018 019import java.io.ByteArrayOutputStream; 020import java.io.UnsupportedEncodingException; 021 022import org.springframework.util.Assert; 023 024/** 025 * Utility class for URI encoding and decoding based on RFC 3986. 026 * Offers encoding methods for the various URI components. 027 * 028 * <p>All {@code encode*(String, String)} methods in this class operate in a similar way: 029 * <ul> 030 * <li>Valid characters for the specific URI component as defined in RFC 3986 stay the same.</li> 031 * <li>All other characters are converted into one or more bytes in the given encoding scheme. 032 * Each of the resulting bytes is written as a hexadecimal string in the "<code>%<i>xy</i></code>" 033 * format.</li> 034 * </ul> 035 * 036 * @author Arjen Poutsma 037 * @author Juergen Hoeller 038 * @since 3.0 039 * @see <a href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a> 040 */ 041public abstract class UriUtils { 042 043 /** 044 * Encode the given URI scheme with the given encoding. 045 * @param scheme the scheme to be encoded 046 * @param encoding the character encoding to encode to 047 * @return the encoded scheme 048 * @throws UnsupportedEncodingException when the given encoding parameter is not supported 049 */ 050 public static String encodeScheme(String scheme, String encoding) throws UnsupportedEncodingException { 051 return HierarchicalUriComponents.encodeUriComponent(scheme, encoding, HierarchicalUriComponents.Type.SCHEME); 052 } 053 054 /** 055 * Encode the given URI authority with the given encoding. 056 * @param authority the authority to be encoded 057 * @param encoding the character encoding to encode to 058 * @return the encoded authority 059 * @throws UnsupportedEncodingException when the given encoding parameter is not supported 060 */ 061 public static String encodeAuthority(String authority, String encoding) throws UnsupportedEncodingException { 062 return HierarchicalUriComponents.encodeUriComponent(authority, encoding, HierarchicalUriComponents.Type.AUTHORITY); 063 } 064 065 /** 066 * Encode the given URI user info with the given encoding. 067 * @param userInfo the user info to be encoded 068 * @param encoding the character encoding to encode to 069 * @return the encoded user info 070 * @throws UnsupportedEncodingException when the given encoding parameter is not supported 071 */ 072 public static String encodeUserInfo(String userInfo, String encoding) throws UnsupportedEncodingException { 073 return HierarchicalUriComponents.encodeUriComponent(userInfo, encoding, HierarchicalUriComponents.Type.USER_INFO); 074 } 075 076 /** 077 * Encode the given URI host with the given encoding. 078 * @param host the host to be encoded 079 * @param encoding the character encoding to encode to 080 * @return the encoded host 081 * @throws UnsupportedEncodingException when the given encoding parameter is not supported 082 */ 083 public static String encodeHost(String host, String encoding) throws UnsupportedEncodingException { 084 return HierarchicalUriComponents.encodeUriComponent(host, encoding, HierarchicalUriComponents.Type.HOST_IPV4); 085 } 086 087 /** 088 * Encode the given URI port with the given encoding. 089 * @param port the port to be encoded 090 * @param encoding the character encoding to encode to 091 * @return the encoded port 092 * @throws UnsupportedEncodingException when the given encoding parameter is not supported 093 */ 094 public static String encodePort(String port, String encoding) throws UnsupportedEncodingException { 095 return HierarchicalUriComponents.encodeUriComponent(port, encoding, HierarchicalUriComponents.Type.PORT); 096 } 097 098 /** 099 * Encode the given URI path with the given encoding. 100 * @param path the path to be encoded 101 * @param encoding the character encoding to encode to 102 * @return the encoded path 103 * @throws UnsupportedEncodingException when the given encoding parameter is not supported 104 */ 105 public static String encodePath(String path, String encoding) throws UnsupportedEncodingException { 106 return HierarchicalUriComponents.encodeUriComponent(path, encoding, HierarchicalUriComponents.Type.PATH); 107 } 108 109 /** 110 * Encode the given URI path segment with the given encoding. 111 * @param segment the segment to be encoded 112 * @param encoding the character encoding to encode to 113 * @return the encoded segment 114 * @throws UnsupportedEncodingException when the given encoding parameter is not supported 115 */ 116 public static String encodePathSegment(String segment, String encoding) throws UnsupportedEncodingException { 117 return HierarchicalUriComponents.encodeUriComponent(segment, encoding, HierarchicalUriComponents.Type.PATH_SEGMENT); 118 } 119 120 /** 121 * Encode the given URI query with the given encoding. 122 * @param query the query to be encoded 123 * @param encoding the character encoding to encode to 124 * @return the encoded query 125 * @throws UnsupportedEncodingException when the given encoding parameter is not supported 126 */ 127 public static String encodeQuery(String query, String encoding) throws UnsupportedEncodingException { 128 return HierarchicalUriComponents.encodeUriComponent(query, encoding, HierarchicalUriComponents.Type.QUERY); 129 } 130 131 /** 132 * Encode the given URI query parameter with the given encoding. 133 * @param queryParam the query parameter to be encoded 134 * @param encoding the character encoding to encode to 135 * @return the encoded query parameter 136 * @throws UnsupportedEncodingException when the given encoding parameter is not supported 137 */ 138 public static String encodeQueryParam(String queryParam, String encoding) throws UnsupportedEncodingException { 139 return HierarchicalUriComponents.encodeUriComponent(queryParam, encoding, HierarchicalUriComponents.Type.QUERY_PARAM); 140 } 141 142 /** 143 * Encode the given URI fragment with the given encoding. 144 * @param fragment the fragment to be encoded 145 * @param encoding the character encoding to encode to 146 * @return the encoded fragment 147 * @throws UnsupportedEncodingException when the given encoding parameter is not supported 148 */ 149 public static String encodeFragment(String fragment, String encoding) throws UnsupportedEncodingException { 150 return HierarchicalUriComponents.encodeUriComponent(fragment, encoding, HierarchicalUriComponents.Type.FRAGMENT); 151 } 152 153 /** 154 * Encode characters outside the unreserved character set as defined in 155 * <a href="https://tools.ietf.org/html/rfc3986#section-2">RFC 3986 Section 2</a>. 156 * <p>This can be used to ensure the given String will not contain any 157 * characters with reserved URI meaning regardless of URI component. 158 * @param source the String to be encoded 159 * @param encoding the character encoding to encode to 160 * @return the encoded String 161 * @throws UnsupportedEncodingException when the given encoding parameter is not supported 162 */ 163 public static String encode(String source, String encoding) throws UnsupportedEncodingException { 164 HierarchicalUriComponents.Type type = HierarchicalUriComponents.Type.URI; 165 return HierarchicalUriComponents.encodeUriComponent(source, encoding, type); 166 } 167 168 /** 169 * Decode the given encoded URI component. 170 * <ul> 171 * <li>Alphanumeric characters {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"}, and 172 * {@code "0"} through {@code "9"} stay the same.</li> 173 * <li>Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same.</li> 174 * <li>A sequence "{@code %<i>xy</i>}" is interpreted as a hexadecimal representation of the character.</li> 175 * </ul> 176 * @param source the encoded String 177 * @param encoding the encoding 178 * @return the decoded value 179 * @throws IllegalArgumentException when the given source contains invalid encoded sequences 180 * @throws UnsupportedEncodingException when the given encoding parameter is not supported 181 * @see java.net.URLDecoder#decode(String, String) 182 */ 183 public static String decode(String source, String encoding) throws UnsupportedEncodingException { 184 if (source == null) { 185 return null; 186 } 187 Assert.hasLength(encoding, "Encoding must not be empty"); 188 int length = source.length(); 189 ByteArrayOutputStream bos = new ByteArrayOutputStream(length); 190 boolean changed = false; 191 for (int i = 0; i < length; i++) { 192 int ch = source.charAt(i); 193 if (ch == '%') { 194 if ((i + 2) < length) { 195 char hex1 = source.charAt(i + 1); 196 char hex2 = source.charAt(i + 2); 197 int u = Character.digit(hex1, 16); 198 int l = Character.digit(hex2, 16); 199 if (u == -1 || l == -1) { 200 throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\""); 201 } 202 bos.write((char) ((u << 4) + l)); 203 i += 2; 204 changed = true; 205 } 206 else { 207 throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\""); 208 } 209 } 210 else { 211 bos.write(ch); 212 } 213 } 214 return (changed ? new String(bos.toByteArray(), encoding) : source); 215 } 216 217 /** 218 * Extract the file extension from the given URI path. 219 * @param path the URI path (e.g. "/products/index.html") 220 * @return the extracted file extension (e.g. "html") 221 * @since 4.3.2 222 */ 223 public static String extractFileExtension(String path) { 224 int end = path.indexOf('?'); 225 int fragmentIndex = path.indexOf('#'); 226 if (fragmentIndex != -1 && (end == -1 || fragmentIndex < end)) { 227 end = fragmentIndex; 228 } 229 if (end == -1) { 230 end = path.length(); 231 } 232 int begin = path.lastIndexOf('/', end) + 1; 233 int paramIndex = path.indexOf(';', begin); 234 end = (paramIndex != -1 && paramIndex < end ? paramIndex : end); 235 int extIndex = path.lastIndexOf('.', end); 236 if (extIndex != -1 && extIndex > begin) { 237 return path.substring(extIndex + 1, end); 238 } 239 return null; 240 } 241 242}