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}