PageRenderTime 26ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/org/elasticsearch/rest/support/RestUtils.java

https://bitbucket.org/dkartaschew/elasticsearch
Java | 219 lines | 135 code | 13 blank | 71 comment | 57 complexity | 23c8fc5b8c4e66c185b2dcea4a704791 MD5 | raw file
  1. /*
  2. * Licensed to ElasticSearch and Shay Banon under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. ElasticSearch licenses this
  6. * file to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. package org.elasticsearch.rest.support;
  20. import com.google.common.base.Charsets;
  21. import org.elasticsearch.common.Nullable;
  22. import org.elasticsearch.common.path.PathTrie;
  23. import java.nio.charset.Charset;
  24. import java.util.Map;
  25. /**
  26. *
  27. */
  28. public class RestUtils {
  29. public static PathTrie.Decoder REST_DECODER = new PathTrie.Decoder() {
  30. @Override
  31. public String decode(String value) {
  32. return RestUtils.decodeComponent(value);
  33. }
  34. };
  35. public static boolean isBrowser(@Nullable String userAgent) {
  36. if (userAgent == null) {
  37. return false;
  38. }
  39. // chrome, safari, firefox, ie
  40. if (userAgent.startsWith("Mozilla")) {
  41. return true;
  42. }
  43. return false;
  44. }
  45. public static void decodeQueryString(String s, int fromIndex, Map<String, String> params) {
  46. if (fromIndex < 0) {
  47. return;
  48. }
  49. if (fromIndex >= s.length()) {
  50. return;
  51. }
  52. String name = null;
  53. int pos = fromIndex; // Beginning of the unprocessed region
  54. int i; // End of the unprocessed region
  55. char c = 0; // Current character
  56. for (i = fromIndex; i < s.length(); i++) {
  57. c = s.charAt(i);
  58. if (c == '=' && name == null) {
  59. if (pos != i) {
  60. name = decodeComponent(s.substring(pos, i));
  61. }
  62. pos = i + 1;
  63. } else if (c == '&') {
  64. if (name == null && pos != i) {
  65. // We haven't seen an `=' so far but moved forward.
  66. // Must be a param of the form '&a&' so add it with
  67. // an empty value.
  68. addParam(params, decodeComponent(s.substring(pos, i)), "");
  69. } else if (name != null) {
  70. addParam(params, name, decodeComponent(s.substring(pos, i)));
  71. name = null;
  72. }
  73. pos = i + 1;
  74. }
  75. }
  76. if (pos != i) { // Are there characters we haven't dealt with?
  77. if (name == null) { // Yes and we haven't seen any `='.
  78. addParam(params, decodeComponent(s.substring(pos, i)), "");
  79. } else { // Yes and this must be the last value.
  80. addParam(params, name, decodeComponent(s.substring(pos, i)));
  81. }
  82. } else if (name != null) { // Have we seen a name without value?
  83. addParam(params, name, "");
  84. }
  85. }
  86. private static void addParam(Map<String, String> params, String name, String value) {
  87. params.put(name, value);
  88. }
  89. /**
  90. * Decodes a bit of an URL encoded by a browser.
  91. * <p/>
  92. * This is equivalent to calling {@link #decodeComponent(String, Charset)}
  93. * with the UTF-8 charset (recommended to comply with RFC 3986, Section 2).
  94. *
  95. * @param s The string to decode (can be empty).
  96. * @return The decoded string, or {@code s} if there's nothing to decode.
  97. * If the string to decode is {@code null}, returns an empty string.
  98. * @throws IllegalArgumentException if the string contains a malformed
  99. * escape sequence.
  100. */
  101. public static String decodeComponent(final String s) {
  102. return decodeComponent(s, Charsets.UTF_8);
  103. }
  104. /**
  105. * Decodes a bit of an URL encoded by a browser.
  106. * <p/>
  107. * The string is expected to be encoded as per RFC 3986, Section 2.
  108. * This is the encoding used by JavaScript functions {@code encodeURI}
  109. * and {@code encodeURIComponent}, but not {@code escape}. For example
  110. * in this encoding, &eacute; (in Unicode {@code U+00E9} or in UTF-8
  111. * {@code 0xC3 0xA9}) is encoded as {@code %C3%A9} or {@code %c3%a9}.
  112. * <p/>
  113. * This is essentially equivalent to calling
  114. * <code>{@link java.net.URLDecoder URLDecoder}.{@link
  115. * java.net.URLDecoder#decode(String, String)}</code>
  116. * except that it's over 2x faster and generates less garbage for the GC.
  117. * Actually this function doesn't allocate any memory if there's nothing
  118. * to decode, the argument itself is returned.
  119. *
  120. * @param s The string to decode (can be empty).
  121. * @param charset The charset to use to decode the string (should really
  122. * be {@link Charsets#UTF_8}.
  123. * @return The decoded string, or {@code s} if there's nothing to decode.
  124. * If the string to decode is {@code null}, returns an empty string.
  125. * @throws IllegalArgumentException if the string contains a malformed
  126. * escape sequence.
  127. */
  128. @SuppressWarnings("fallthrough")
  129. public static String decodeComponent(final String s, final Charset charset) {
  130. if (s == null) {
  131. return "";
  132. }
  133. final int size = s.length();
  134. boolean modified = false;
  135. for (int i = 0; i < size; i++) {
  136. final char c = s.charAt(i);
  137. switch (c) {
  138. case '%':
  139. i++; // We can skip at least one char, e.g. `%%'.
  140. // Fall through.
  141. case '+':
  142. modified = true;
  143. break;
  144. }
  145. }
  146. if (!modified) {
  147. return s;
  148. }
  149. final byte[] buf = new byte[size];
  150. int pos = 0; // position in `buf'.
  151. for (int i = 0; i < size; i++) {
  152. char c = s.charAt(i);
  153. switch (c) {
  154. case '+':
  155. buf[pos++] = ' '; // "+" -> " "
  156. break;
  157. case '%':
  158. if (i == size - 1) {
  159. throw new IllegalArgumentException("unterminated escape"
  160. + " sequence at end of string: " + s);
  161. }
  162. c = s.charAt(++i);
  163. if (c == '%') {
  164. buf[pos++] = '%'; // "%%" -> "%"
  165. break;
  166. } else if (i == size - 1) {
  167. throw new IllegalArgumentException("partial escape"
  168. + " sequence at end of string: " + s);
  169. }
  170. c = decodeHexNibble(c);
  171. final char c2 = decodeHexNibble(s.charAt(++i));
  172. if (c == Character.MAX_VALUE || c2 == Character.MAX_VALUE) {
  173. throw new IllegalArgumentException(
  174. "invalid escape sequence `%" + s.charAt(i - 1)
  175. + s.charAt(i) + "' at index " + (i - 2)
  176. + " of: " + s);
  177. }
  178. c = (char) (c * 16 + c2);
  179. // Fall through.
  180. default:
  181. buf[pos++] = (byte) c;
  182. break;
  183. }
  184. }
  185. return new String(buf, 0, pos, charset);
  186. }
  187. /**
  188. * Helper to decode half of a hexadecimal number from a string.
  189. *
  190. * @param c The ASCII character of the hexadecimal number to decode.
  191. * Must be in the range {@code [0-9a-fA-F]}.
  192. * @return The hexadecimal value represented in the ASCII character
  193. * given, or {@link Character#MAX_VALUE} if the character is invalid.
  194. */
  195. private static char decodeHexNibble(final char c) {
  196. if ('0' <= c && c <= '9') {
  197. return (char) (c - '0');
  198. } else if ('a' <= c && c <= 'f') {
  199. return (char) (c - 'a' + 10);
  200. } else if ('A' <= c && c <= 'F') {
  201. return (char) (c - 'A' + 10);
  202. } else {
  203. return Character.MAX_VALUE;
  204. }
  205. }
  206. }