Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
ParameterParser |
|
| 4.5;4,5 |
1 | /* | |
2 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
3 | * contributor license agreements. See the NOTICE file distributed with | |
4 | * this work for additional information regarding copyright ownership. | |
5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
6 | * (the "License"); you may not use this file except in compliance with | |
7 | * the License. You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, software | |
12 | * distributed under the License is distributed on an "AS IS" BASIS, | |
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | * See the License for the specific language governing permissions and | |
15 | * limitations under the License. | |
16 | */ | |
17 | package org.apache.commons.fileupload; | |
18 | ||
19 | import java.io.UnsupportedEncodingException; | |
20 | import java.util.HashMap; | |
21 | import java.util.Locale; | |
22 | import java.util.Map; | |
23 | ||
24 | import org.apache.commons.fileupload.util.mime.MimeUtility; | |
25 | ||
26 | /** | |
27 | * A simple parser intended to parse sequences of name/value pairs. | |
28 | * | |
29 | * Parameter values are expected to be enclosed in quotes if they | |
30 | * contain unsafe characters, such as '=' characters or separators. | |
31 | * Parameter values are optional and can be omitted. | |
32 | * | |
33 | * <p> | |
34 | * <code>param1 = value; param2 = "anything goes; really"; param3</code> | |
35 | * </p> | |
36 | */ | |
37 | public class ParameterParser { | |
38 | ||
39 | /** | |
40 | * String to be parsed. | |
41 | */ | |
42 | 6456 | private char[] chars = null; |
43 | ||
44 | /** | |
45 | * Current position in the string. | |
46 | */ | |
47 | 6456 | private int pos = 0; |
48 | ||
49 | /** | |
50 | * Maximum position in the string. | |
51 | */ | |
52 | 6456 | private int len = 0; |
53 | ||
54 | /** | |
55 | * Start of a token. | |
56 | */ | |
57 | 6456 | private int i1 = 0; |
58 | ||
59 | /** | |
60 | * End of a token. | |
61 | */ | |
62 | 6456 | private int i2 = 0; |
63 | ||
64 | /** | |
65 | * Whether names stored in the map should be converted to lower case. | |
66 | */ | |
67 | 6456 | private boolean lowerCaseNames = false; |
68 | ||
69 | /** | |
70 | * Default ParameterParser constructor. | |
71 | */ | |
72 | public ParameterParser() { | |
73 | 6456 | super(); |
74 | 6456 | } |
75 | ||
76 | /** | |
77 | * Are there any characters left to parse? | |
78 | * | |
79 | * @return {@code true} if there are unparsed characters, | |
80 | * {@code false} otherwise. | |
81 | */ | |
82 | private boolean hasChar() { | |
83 | 219097 | return this.pos < this.len; |
84 | } | |
85 | ||
86 | /** | |
87 | * A helper method to process the parsed token. This method removes | |
88 | * leading and trailing blanks as well as enclosing quotation marks, | |
89 | * when necessary. | |
90 | * | |
91 | * @param quoted {@code true} if quotation marks are expected, | |
92 | * {@code false} otherwise. | |
93 | * @return the token | |
94 | */ | |
95 | private String getToken(boolean quoted) { | |
96 | // Trim leading white spaces | |
97 | 25909 | while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) { |
98 | 6508 | i1++; |
99 | } | |
100 | // Trim trailing white spaces | |
101 | 19422 | while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) { |
102 | 21 | i2--; |
103 | } | |
104 | // Strip away quotation marks if necessary | |
105 | 19401 | if (quoted |
106 | && ((i2 - i1) >= 2) | |
107 | && (chars[i1] == '"') | |
108 | && (chars[i2 - 1] == '"')) { | |
109 | 6430 | i1++; |
110 | 6430 | i2--; |
111 | } | |
112 | 19401 | String result = null; |
113 | 19401 | if (i2 > i1) { |
114 | 19392 | result = new String(chars, i1, i2 - i1); |
115 | } | |
116 | 19401 | return result; |
117 | } | |
118 | ||
119 | /** | |
120 | * Tests if the given character is present in the array of characters. | |
121 | * | |
122 | * @param ch the character to test for presense in the array of characters | |
123 | * @param charray the array of characters to test against | |
124 | * | |
125 | * @return {@code true} if the character is present in the array of | |
126 | * characters, {@code false} otherwise. | |
127 | */ | |
128 | private boolean isOneOf(char ch, final char[] charray) { | |
129 | 110965 | boolean result = false; |
130 | 306633 | for (char element : charray) { |
131 | 208629 | if (ch == element) { |
132 | 12961 | result = true; |
133 | 12961 | break; |
134 | } | |
135 | } | |
136 | 110965 | return result; |
137 | } | |
138 | ||
139 | /** | |
140 | * Parses out a token until any of the given terminators | |
141 | * is encountered. | |
142 | * | |
143 | * @param terminators the array of terminating characters. Any of these | |
144 | * characters when encountered signify the end of the token | |
145 | * | |
146 | * @return the token | |
147 | */ | |
148 | private String parseToken(final char[] terminators) { | |
149 | char ch; | |
150 | 12921 | i1 = pos; |
151 | 12921 | i2 = pos; |
152 | 104158 | while (hasChar()) { |
153 | 104144 | ch = chars[pos]; |
154 | 104144 | if (isOneOf(ch, terminators)) { |
155 | 12907 | break; |
156 | } | |
157 | 91237 | i2++; |
158 | 91237 | pos++; |
159 | } | |
160 | 12921 | return getToken(false); |
161 | } | |
162 | ||
163 | /** | |
164 | * Parses out a token until any of the given terminators | |
165 | * is encountered outside the quotation marks. | |
166 | * | |
167 | * @param terminators the array of terminating characters. Any of these | |
168 | * characters when encountered outside the quotation marks signify the end | |
169 | * of the token | |
170 | * | |
171 | * @return the token | |
172 | */ | |
173 | private String parseQuotedToken(final char[] terminators) { | |
174 | char ch; | |
175 | 6480 | i1 = pos; |
176 | 6480 | i2 = pos; |
177 | 6480 | boolean quoted = false; |
178 | 6480 | boolean charEscaped = false; |
179 | 69736 | while (hasChar()) { |
180 | 63310 | ch = chars[pos]; |
181 | 63310 | if (!quoted && isOneOf(ch, terminators)) { |
182 | 54 | break; |
183 | } | |
184 | 63256 | if (!charEscaped && ch == '"') { |
185 | 12862 | quoted = !quoted; |
186 | } | |
187 | 63256 | charEscaped = (!charEscaped && ch == '\\'); |
188 | 63256 | i2++; |
189 | 63256 | pos++; |
190 | ||
191 | } | |
192 | 6480 | return getToken(true); |
193 | } | |
194 | ||
195 | /** | |
196 | * Returns {@code true} if parameter names are to be converted to lower | |
197 | * case when name/value pairs are parsed. | |
198 | * | |
199 | * @return {@code true} if parameter names are to be | |
200 | * converted to lower case when name/value pairs are parsed. | |
201 | * Otherwise returns {@code false} | |
202 | */ | |
203 | public boolean isLowerCaseNames() { | |
204 | 0 | return this.lowerCaseNames; |
205 | } | |
206 | ||
207 | /** | |
208 | * Sets the flag if parameter names are to be converted to lower case when | |
209 | * name/value pairs are parsed. | |
210 | * | |
211 | * @param b {@code true} if parameter names are to be | |
212 | * converted to lower case when name/value pairs are parsed. | |
213 | * {@code false} otherwise. | |
214 | */ | |
215 | public void setLowerCaseNames(boolean b) { | |
216 | 6452 | this.lowerCaseNames = b; |
217 | 6452 | } |
218 | ||
219 | /** | |
220 | * Extracts a map of name/value pairs from the given string. Names are | |
221 | * expected to be unique. Multiple separators may be specified and | |
222 | * the earliest found in the input string is used. | |
223 | * | |
224 | * @param str the string that contains a sequence of name/value pairs | |
225 | * @param separators the name/value pairs separators | |
226 | * | |
227 | * @return a map of name/value pairs | |
228 | */ | |
229 | public Map<String, String> parse(final String str, char[] separators) { | |
230 | 45 | if (separators == null || separators.length == 0) { |
231 | 0 | return new HashMap<String, String>(); |
232 | } | |
233 | 45 | char separator = separators[0]; |
234 | 45 | if (str != null) { |
235 | 45 | int idx = str.length(); |
236 | 135 | for (char separator2 : separators) { |
237 | 90 | int tmp = str.indexOf(separator2); |
238 | 90 | if (tmp != -1 && tmp < idx) { |
239 | 44 | idx = tmp; |
240 | 44 | separator = separator2; |
241 | } | |
242 | } | |
243 | } | |
244 | 45 | return parse(str, separator); |
245 | } | |
246 | ||
247 | /** | |
248 | * Extracts a map of name/value pairs from the given string. Names are | |
249 | * expected to be unique. | |
250 | * | |
251 | * @param str the string that contains a sequence of name/value pairs | |
252 | * @param separator the name/value pairs separator | |
253 | * | |
254 | * @return a map of name/value pairs | |
255 | */ | |
256 | public Map<String, String> parse(final String str, char separator) { | |
257 | 6464 | if (str == null) { |
258 | 24 | return new HashMap<String, String>(); |
259 | } | |
260 | 6440 | return parse(str.toCharArray(), separator); |
261 | } | |
262 | ||
263 | /** | |
264 | * Extracts a map of name/value pairs from the given array of | |
265 | * characters. Names are expected to be unique. | |
266 | * | |
267 | * @param charArray the array of characters that contains a sequence of | |
268 | * name/value pairs | |
269 | * @param separator the name/value pairs separator | |
270 | * | |
271 | * @return a map of name/value pairs | |
272 | */ | |
273 | public Map<String, String> parse(final char[] charArray, char separator) { | |
274 | 6440 | if (charArray == null) { |
275 | 0 | return new HashMap<String, String>(); |
276 | } | |
277 | 6440 | return parse(charArray, 0, charArray.length, separator); |
278 | } | |
279 | ||
280 | /** | |
281 | * Extracts a map of name/value pairs from the given array of | |
282 | * characters. Names are expected to be unique. | |
283 | * | |
284 | * @param charArray the array of characters that contains a sequence of | |
285 | * name/value pairs | |
286 | * @param offset - the initial offset. | |
287 | * @param length - the length. | |
288 | * @param separator the name/value pairs separator | |
289 | * | |
290 | * @return a map of name/value pairs | |
291 | */ | |
292 | public Map<String, String> parse( | |
293 | final char[] charArray, | |
294 | int offset, | |
295 | int length, | |
296 | char separator) { | |
297 | ||
298 | 6440 | if (charArray == null) { |
299 | 0 | return new HashMap<String, String>(); |
300 | } | |
301 | 6440 | HashMap<String, String> params = new HashMap<String, String>(); |
302 | 6440 | this.chars = charArray; |
303 | 6440 | this.pos = offset; |
304 | 6440 | this.len = length; |
305 | ||
306 | 6440 | String paramName = null; |
307 | 6440 | String paramValue = null; |
308 | 19361 | while (hasChar()) { |
309 | 12921 | paramName = parseToken(new char[] { |
310 | '=', separator }); | |
311 | 12921 | paramValue = null; |
312 | 12921 | if (hasChar() && (charArray[pos] == '=')) { |
313 | 6480 | pos++; // skip '=' |
314 | 6480 | paramValue = parseQuotedToken(new char[] { |
315 | separator }); | |
316 | ||
317 | 6480 | if (paramValue != null) { |
318 | try { | |
319 | 6475 | paramValue = MimeUtility.decodeText(paramValue); |
320 | 0 | } catch (UnsupportedEncodingException e) { |
321 | // let's keep the original value in this case | |
322 | 6475 | } |
323 | } | |
324 | } | |
325 | 12921 | if (hasChar() && (charArray[pos] == separator)) { |
326 | 6481 | pos++; // skip separator |
327 | } | |
328 | 12921 | if ((paramName != null) && (paramName.length() > 0)) { |
329 | 12917 | if (this.lowerCaseNames) { |
330 | 12892 | paramName = paramName.toLowerCase(Locale.ENGLISH); |
331 | } | |
332 | ||
333 | 12917 | params.put(paramName, paramValue); |
334 | } | |
335 | } | |
336 | 6440 | return params; |
337 | } | |
338 | ||
339 | } |