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.util.HashMap;
20 import java.util.Map;
21
22 /**
23 * A simple parser intended to parse sequences of name/value pairs.
24 * Parameter values are exptected to be enclosed in quotes if they
25 * contain unsafe characters, such as '=' characters or separators.
26 * Parameter values are optional and can be omitted.
27 *
28 * <p>
29 * <code>param1 = value; param2 = "anything goes; really"; param3</code>
30 * </p>
31 *
32 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
33 */
34
35 public class ParameterParser {
36 /**
37 * String to be parsed.
38 */
39 private char[] chars = null;
40
41 /**
42 * Current position in the string.
43 */
44 private int pos = 0;
45
46 /**
47 * Maximum position in the string.
48 */
49 private int len = 0;
50
51 /**
52 * Start of a token.
53 */
54 private int i1 = 0;
55
56 /**
57 * End of a token.
58 */
59 private int i2 = 0;
60
61 /**
62 * Whether names stored in the map should be converted to lower case.
63 */
64 private boolean lowerCaseNames = false;
65
66 /**
67 * Default ParameterParser constructor.
68 */
69 public ParameterParser() {
70 super();
71 }
72
73 /**
74 * Are there any characters left to parse?
75 *
76 * @return <tt>true</tt> if there are unparsed characters,
77 * <tt>false</tt> otherwise.
78 */
79 private boolean hasChar() {
80 return this.pos < this.len;
81 }
82
83 /**
84 * A helper method to process the parsed token. This method removes
85 * leading and trailing blanks as well as enclosing quotation marks,
86 * when necessary.
87 *
88 * @param quoted <tt>true</tt> if quotation marks are expected,
89 * <tt>false</tt> otherwise.
90 * @return the token
91 */
92 private String getToken(boolean quoted) {
93 // Trim leading white spaces
94 while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) {
95 i1++;
96 }
97 // Trim trailing white spaces
98 while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) {
99 i2--;
100 }
101 // Strip away quotation marks if necessary
102 if (quoted) {
103 if (((i2 - i1) >= 2)
104 && (chars[i1] == '"')
105 && (chars[i2 - 1] == '"')) {
106 i1++;
107 i2--;
108 }
109 }
110 String result = null;
111 if (i2 > i1) {
112 result = new String(chars, i1, i2 - i1);
113 }
114 return result;
115 }
116
117 /**
118 * Tests if the given character is present in the array of characters.
119 *
120 * @param ch the character to test for presense in the array of characters
121 * @param charray the array of characters to test against
122 *
123 * @return <tt>true</tt> if the character is present in the array of
124 * characters, <tt>false</tt> otherwise.
125 */
126 private boolean isOneOf(char ch, final char[] charray) {
127 boolean result = false;
128 for (int i = 0; i < charray.length; i++) {
129 if (ch == charray[i]) {
130 result = true;
131 break;
132 }
133 }
134 return result;
135 }
136
137 /**
138 * Parses out a token until any of the given terminators
139 * is encountered.
140 *
141 * @param terminators the array of terminating characters. Any of these
142 * characters when encountered signify the end of the token
143 *
144 * @return the token
145 */
146 private String parseToken(final char[] terminators) {
147 char ch;
148 i1 = pos;
149 i2 = pos;
150 while (hasChar()) {
151 ch = chars[pos];
152 if (isOneOf(ch, terminators)) {
153 break;
154 }
155 i2++;
156 pos++;
157 }
158 return getToken(false);
159 }
160
161 /**
162 * Parses out a token until any of the given terminators
163 * is encountered outside the quotation marks.
164 *
165 * @param terminators the array of terminating characters. Any of these
166 * characters when encountered outside the quotation marks signify the end
167 * of the token
168 *
169 * @return the token
170 */
171 private String parseQuotedToken(final char[] terminators) {
172 char ch;
173 i1 = pos;
174 i2 = pos;
175 boolean quoted = false;
176 boolean charEscaped = false;
177 while (hasChar()) {
178 ch = chars[pos];
179 if (!quoted && isOneOf(ch, terminators)) {
180 break;
181 }
182 if (!charEscaped && ch == '"') {
183 quoted = !quoted;
184 }
185 charEscaped = (!charEscaped && ch == '\\');
186 i2++;
187 pos++;
188
189 }
190 return getToken(true);
191 }
192
193 /**
194 * Returns <tt>true</tt> if parameter names are to be converted to lower
195 * case when name/value pairs are parsed.
196 *
197 * @return <tt>true</tt> if parameter names are to be
198 * converted to lower case when name/value pairs are parsed.
199 * Otherwise returns <tt>false</tt>
200 */
201 public boolean isLowerCaseNames() {
202 return this.lowerCaseNames;
203 }
204
205 /**
206 * Sets the flag if parameter names are to be converted to lower case when
207 * name/value pairs are parsed.
208 *
209 * @param b <tt>true</tt> if parameter names are to be
210 * converted to lower case when name/value pairs are parsed.
211 * <tt>false</tt> otherwise.
212 */
213 public void setLowerCaseNames(boolean b) {
214 this.lowerCaseNames = b;
215 }
216
217 /**
218 * Extracts a map of name/value pairs from the given string. Names are
219 * expected to be unique. Multiple separators may be specified and
220 * the earliest found in the input string is used.
221 *
222 * @param str the string that contains a sequence of name/value pairs
223 * @param separators the name/value pairs separators
224 *
225 * @return a map of name/value pairs
226 */
227 public Map parse(final String str, char[] separators) {
228 if (separators == null || separators.length == 0) {
229 return new HashMap();
230 }
231 char separator = separators[0];
232 if (str != null) {
233 int idx = str.length();
234 for (int i = 0; i < separators.length; i++) {
235 int tmp = str.indexOf(separators[i]);
236 if (tmp != -1) {
237 if (tmp < idx) {
238 idx = tmp;
239 separator = separators[i];
240 }
241 }
242 }
243 }
244 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 parse(final String str, char separator) {
257 if (str == null) {
258 return new HashMap();
259 }
260 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 chars 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 parse(final char[] chars, char separator) {
274 if (chars == null) {
275 return new HashMap();
276 }
277 return parse(chars, 0, chars.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 chars 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 parse(
293 final char[] chars,
294 int offset,
295 int length,
296 char separator) {
297
298 if (chars == null) {
299 return new HashMap();
300 }
301 HashMap params = new HashMap();
302 this.chars = chars;
303 this.pos = offset;
304 this.len = length;
305
306 String paramName = null;
307 String paramValue = null;
308 while (hasChar()) {
309 paramName = parseToken(new char[] {
310 '=', separator });
311 paramValue = null;
312 if (hasChar() && (chars[pos] == '=')) {
313 pos++; // skip '='
314 paramValue = parseQuotedToken(new char[] {
315 separator });
316 }
317 if (hasChar() && (chars[pos] == separator)) {
318 pos++; // skip separator
319 }
320 if ((paramName != null) && (paramName.length() > 0)) {
321 if (this.lowerCaseNames) {
322 paramName = paramName.toLowerCase();
323 }
324 params.put(paramName, paramValue);
325 }
326 }
327 return params;
328 }
329 }