001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.rest; 014 015import static org.apache.juneau.internal.ArrayUtils.*; 016import static org.apache.juneau.internal.StringUtils.*; 017 018import java.util.*; 019import java.util.regex.*; 020 021import javax.servlet.http.*; 022 023import org.apache.juneau.*; 024import org.apache.juneau.parser.*; 025import org.apache.juneau.uon.*; 026import org.apache.juneau.utils.*; 027 028/** 029 * @deprecated Use {@link org.apache.juneau.rest.util.RestUtils} 030 */ 031@Deprecated 032public final class RestUtils { 033 034 /** 035 * Returns readable text for an HTTP response code. 036 * 037 * @param rc The HTTP response code. 038 * @return Readable text for an HTTP response code, or <jk>null</jk> if it's an invalid code. 039 */ 040 public static String getHttpResponseText(int rc) { 041 return httpMsgs.get(rc); 042 } 043 044 private static Map<Integer,String> httpMsgs = new AMap<Integer,String>() 045 .append(200, "OK") 046 .append(201, "Created") 047 .append(202, "Accepted") 048 .append(203, "Non-Authoritative Information") 049 .append(204, "No Content") 050 .append(205, "Reset Content") 051 .append(206, "Partial Content") 052 .append(300, "Multiple Choices") 053 .append(301, "Moved Permanently") 054 .append(302, "Temporary Redirect") 055 .append(303, "See Other") 056 .append(304, "Not Modified") 057 .append(305, "Use Proxy") 058 .append(307, "Temporary Redirect") 059 .append(400, "Bad Request") 060 .append(401, "Unauthorized") 061 .append(402, "Payment Required") 062 .append(403, "Forbidden") 063 .append(404, "Not Found") 064 .append(405, "Method Not Allowed") 065 .append(406, "Not Acceptable") 066 .append(407, "Proxy Authentication Required") 067 .append(408, "Request Time-Out") 068 .append(409, "Conflict") 069 .append(410, "Gone") 070 .append(411, "Length Required") 071 .append(412, "Precondition Failed") 072 .append(413, "Request Entity Too Large") 073 .append(414, "Request-URI Too Large") 074 .append(415, "Unsupported Media Type") 075 .append(500, "Internal Server Error") 076 .append(501, "Not Implemented") 077 .append(502, "Bad Gateway") 078 .append(503, "Service Unavailable") 079 .append(504, "Gateway Timeout") 080 .append(505, "HTTP Version Not Supported") 081 ; 082 083 /** 084 * Identical to {@link HttpServletRequest#getPathInfo()} but doesn't decode encoded characters. 085 * 086 * @param req The HTTP request 087 * @return The un-decoded path info. 088 */ 089 public static String getPathInfoUndecoded(HttpServletRequest req) { 090 String requestURI = req.getRequestURI(); 091 String contextPath = req.getContextPath(); 092 String servletPath = req.getServletPath(); 093 int l = contextPath.length() + servletPath.length(); 094 if (requestURI.length() == l) 095 return null; 096 return requestURI.substring(l); 097 } 098 099 /** 100 * Efficiently trims the path info part from a request URI. 101 * 102 * <p> 103 * The result is the URI of the servlet itself. 104 * 105 * @param requestURI The value returned by {@link HttpServletRequest#getRequestURL()} 106 * @param contextPath The value returned by {@link HttpServletRequest#getContextPath()} 107 * @param servletPath The value returned by {@link HttpServletRequest#getServletPath()} 108 * @return The same StringBuilder with remainder trimmed. 109 */ 110 public static StringBuffer trimPathInfo(StringBuffer requestURI, String contextPath, String servletPath) { 111 if (servletPath.equals("/")) 112 servletPath = ""; 113 if (contextPath.equals("/")) 114 contextPath = ""; 115 116 try { 117 // Given URL: http://hostname:port/servletPath/extra 118 // We want: http://hostname:port/servletPath 119 int sc = 0; 120 for (int i = 0; i < requestURI.length(); i++) { 121 char c = requestURI.charAt(i); 122 if (c == '/') { 123 sc++; 124 if (sc == 3) { 125 if (servletPath.isEmpty()) { 126 requestURI.setLength(i); 127 return requestURI; 128 } 129 130 // Make sure context path follows the authority. 131 for (int j = 0; j < contextPath.length(); i++, j++) 132 if (requestURI.charAt(i) != contextPath.charAt(j)) 133 throw new Exception("case=1"); 134 135 // Make sure servlet path follows the authority. 136 for (int j = 0; j < servletPath.length(); i++, j++) 137 if (requestURI.charAt(i) != servletPath.charAt(j)) 138 throw new Exception("case=2"); 139 140 // Make sure servlet path isn't a false match (e.g. /foo2 should not match /foo) 141 c = (requestURI.length() == i ? '/' : requestURI.charAt(i)); 142 if (c == '/' || c == '?') { 143 requestURI.setLength(i); 144 return requestURI; 145 } 146 147 throw new Exception("case=3"); 148 } 149 } else if (c == '?') { 150 if (sc != 2) 151 throw new Exception("case=4"); 152 if (servletPath.isEmpty()) { 153 requestURI.setLength(i); 154 return requestURI; 155 } 156 throw new Exception("case=5"); 157 } 158 } 159 if (servletPath.isEmpty()) 160 return requestURI; 161 throw new Exception("case=6"); 162 } catch (Exception e) { 163 throw new FormattedRuntimeException(e, "Could not find servlet path in request URI. URI=''{0}'', servletPath=''{1}''", requestURI, servletPath); 164 } 165 } 166 167 static String[] parseHeader(String s) { 168 int i = s.indexOf(':'); 169 if (i == -1) 170 return null; 171 String name = s.substring(0, i).trim().toLowerCase(Locale.ENGLISH); 172 String val = s.substring(i+1).trim(); 173 return new String[]{name,val}; 174 } 175 176 /** 177 * Parses key/value pairs separated by either : or = 178 */ 179 static String[] parseKeyValuePair(String s) { 180 int i = -1; 181 for (int j = 0; j < s.length() && i < 0; j++) { 182 char c = s.charAt(j); 183 if (c == '=' || c == ':') 184 i = j; 185 } 186 if (i == -1) 187 return null; 188 String name = s.substring(0, i).trim(); 189 String val = s.substring(i+1).trim(); 190 return new String[]{name,val}; 191 } 192 193 static String resolveNewlineSeparatedAnnotation(String[] value, String fromParent) { 194 if (value.length == 0) 195 return fromParent; 196 197 List<String> l = new ArrayList<>(); 198 for (String v : value) { 199 if (! "INHERIT".equals(v)) 200 l.add(v); 201 else if (fromParent != null) 202 l.addAll(Arrays.asList(fromParent)); 203 } 204 return join(l, '\n'); 205 } 206 207 private static final Pattern INDEXED_LINK_PATTERN = Pattern.compile("(?s)(\\S*)\\[(\\d+)\\]\\:(.*)"); 208 209 static String[] resolveLinks(String[] links, String[] parentLinks) { 210 if (links.length == 0) 211 return parentLinks; 212 213 List<String> list = new ArrayList<>(); 214 for (String l : links) { 215 if ("INHERIT".equals(l)) 216 list.addAll(Arrays.asList(parentLinks)); 217 else if (l.indexOf('[') != -1 && INDEXED_LINK_PATTERN.matcher(l).matches()) { 218 Matcher lm = INDEXED_LINK_PATTERN.matcher(l); 219 lm.matches(); 220 String key = lm.group(1); 221 int index = Math.min(list.size(), Integer.parseInt(lm.group(2))); 222 String remainder = lm.group(3); 223 list.add(index, key.isEmpty() ? remainder : key + ":" + remainder); 224 } else { 225 list.add(l); 226 } 227 } 228 return list.toArray(new String[list.size()]); 229 } 230 231 static String[] resolveContent(String[] content, String[] parentContent) { 232 if (content.length == 0) 233 return parentContent; 234 235 List<String> list = new ArrayList<>(); 236 for (String l : content) { 237 if ("INHERIT".equals(l)) { 238 list.addAll(Arrays.asList(parentContent)); 239 } else if ("NONE".equals(l)) { 240 return new String[0]; 241 } else { 242 list.add(l); 243 } 244 } 245 return list.toArray(new String[list.size()]); 246 } 247 248 /** 249 * Parses a URL query string or form-data body. 250 * 251 * @param qs A reader or string containing the query string to parse. 252 * @return A new map containing the parsed query. 253 * @throws Exception 254 */ 255 public static Map<String,String[]> parseQuery(Object qs) throws Exception { 256 return parseQuery(qs, null); 257 } 258 259 /** 260 * Same as {@link #parseQuery(Object)} but allows you to specify the map to insert values into. 261 * 262 * @param qs A reader containing the query string to parse. 263 * @param map The map to pass the values into. 264 * @return The same map passed in, or a new map if it was <jk>null</jk>. 265 * @throws Exception 266 */ 267 public static Map<String,String[]> parseQuery(Object qs, Map<String,String[]> map) throws Exception { 268 269 Map<String,String[]> m = map == null ? new TreeMap<String,String[]>() : map; 270 271 if (qs == null || ((qs instanceof CharSequence) && isEmpty(qs))) 272 return m; 273 274 try (ParserPipe p = new ParserPipe(qs, false, false, false, false, null, null)) { 275 276 final int S1=1; // Looking for attrName start. 277 final int S2=2; // Found attrName start, looking for = or & or end. 278 final int S3=3; // Found =, looking for valStart. 279 final int S4=4; // Found valStart, looking for & or end. 280 281 try (UonReader r = new UonReader(p, true)) { 282 int c = r.peekSkipWs(); 283 if (c == '?') 284 r.read(); 285 286 int state = S1; 287 String currAttr = null; 288 while (c != -1) { 289 c = r.read(); 290 if (state == S1) { 291 if (c != -1) { 292 r.unread(); 293 r.mark(); 294 state = S2; 295 } 296 } else if (state == S2) { 297 if (c == -1) { 298 add(m, r.getMarked(), null); 299 } else if (c == '\u0001') { 300 m.put(r.getMarked(0,-1), null); 301 state = S1; 302 } else if (c == '\u0002') { 303 currAttr = r.getMarked(0,-1); 304 state = S3; 305 } 306 } else if (state == S3) { 307 if (c == -1 || c == '\u0001') { 308 add(m, currAttr, ""); 309 } else { 310 if (c == '\u0002') 311 r.replace('='); 312 r.unread(); 313 r.mark(); 314 state = S4; 315 } 316 } else if (state == S4) { 317 if (c == -1) { 318 add(m, currAttr, r.getMarked()); 319 } else if (c == '\u0001') { 320 add(m, currAttr, r.getMarked(0,-1)); 321 state = S1; 322 } else if (c == '\u0002') { 323 r.replace('='); 324 } 325 } 326 } 327 } 328 329 return m; 330 } 331 } 332 333 private static void add(Map<String,String[]> m, String key, String val) { 334 boolean b = m.containsKey(key); 335 if (val == null) { 336 if (! b) 337 m.put(key, null); 338 } else if (b && m.get(key) != null) { 339 m.put(key, append(m.get(key), val)); 340 } else { 341 m.put(key, new String[]{val}); 342 } 343 } 344}