1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.fileupload;
17
18 import java.util.HashMap;
19 import java.util.Map;
20
21 /***
22 * A simple parser intended to parse sequences of name/value pairs.
23 * Parameter values are exptected to be enclosed in quotes if they
24 * contain unsafe characters, such as '=' characters or separators.
25 * Parameter values are optional and can be omitted.
26 *
27 * <p>
28 * <code>param1 = value; param2 = "anything goes; really"; param3</code>
29 * </p>
30 *
31 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
32 */
33
34 public class ParameterParser {
35 /***
36 * String to be parsed.
37 */
38 private char[] chars = null;
39
40 /***
41 * Current position in the string.
42 */
43 private int pos = 0;
44
45 /***
46 * Maximum position in the string.
47 */
48 private int len = 0;
49
50 /***
51 * Start of a token.
52 */
53 private int i1 = 0;
54
55 /***
56 * End of a token.
57 */
58 private int i2 = 0;
59
60 /***
61 * Whether names stored in the map should be converted to lower case.
62 */
63 private boolean lowerCaseNames = false;
64
65 /***
66 * Default ParameterParser constructor.
67 */
68 public ParameterParser() {
69 super();
70 }
71
72 /***
73 * Are there any characters left to parse?
74 *
75 * @return <tt>true</tt> if there are unparsed characters,
76 * <tt>false</tt> otherwise.
77 */
78 private boolean hasChar() {
79 return this.pos < this.len;
80 }
81
82 /***
83 * A helper method to process the parsed token. This method removes
84 * leading and trailing blanks as well as enclosing quotation marks,
85 * when necessary.
86 *
87 * @param quoted <tt>true</tt> if quotation marks are expected,
88 * <tt>false</tt> otherwise.
89 * @return the token
90 */
91 private String getToken(boolean quoted) {
92
93 while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) {
94 i1++;
95 }
96
97 while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) {
98 i2--;
99 }
100
101 if (quoted) {
102 if (((i2 - i1) >= 2)
103 && (chars[i1] == '"')
104 && (chars[i2 - 1] == '"')) {
105 i1++;
106 i2--;
107 }
108 }
109 String result = null;
110 if (i2 > i1) {
111 result = new String(chars, i1, i2 - i1);
112 }
113 return result;
114 }
115
116 /***
117 * Tests if the given character is present in the array of characters.
118 *
119 * @param ch the character to test for presense in the array of characters
120 * @param charray the array of characters to test against
121 *
122 * @return <tt>true</tt> if the character is present in the array of
123 * characters, <tt>false</tt> otherwise.
124 */
125 private boolean isOneOf(char ch, final char[] charray) {
126 boolean result = false;
127 for (int i = 0; i < charray.length; i++) {
128 if (ch == charray[i]) {
129 result = true;
130 break;
131 }
132 }
133 return result;
134 }
135
136 /***
137 * Parses out a token until any of the given terminators
138 * is encountered.
139 *
140 * @param terminators the array of terminating characters. Any of these
141 * characters when encountered signify the end of the token
142 *
143 * @return the token
144 */
145 private String parseToken(final char[] terminators) {
146 char ch;
147 i1 = pos;
148 i2 = pos;
149 while (hasChar()) {
150 ch = chars[pos];
151 if (isOneOf(ch, terminators)) {
152 break;
153 }
154 i2++;
155 pos++;
156 }
157 return getToken(false);
158 }
159
160 /***
161 * Parses out a token until any of the given terminators
162 * is encountered outside the quotation marks.
163 *
164 * @param terminators the array of terminating characters. Any of these
165 * characters when encountered outside the quotation marks signify the end
166 * of the token
167 *
168 * @return the token
169 */
170 private String parseQuotedToken(final char[] terminators) {
171 char ch;
172 i1 = pos;
173 i2 = pos;
174 boolean quoted = false;
175 boolean charEscaped = false;
176 while (hasChar()) {
177 ch = chars[pos];
178 if (!quoted && isOneOf(ch, terminators)) {
179 break;
180 }
181 if (!charEscaped && ch == '"') {
182 quoted = !quoted;
183 }
184 charEscaped = (!charEscaped && ch == '//');
185 i2++;
186 pos++;
187
188 }
189 return getToken(true);
190 }
191
192 /***
193 * Returns <tt>true</tt> if parameter names are to be converted to lower
194 * case when name/value pairs are parsed.
195 *
196 * @return <tt>true</tt> if parameter names are to be
197 * converted to lower case when name/value pairs are parsed.
198 * Otherwise returns <tt>false</tt>
199 */
200 public boolean isLowerCaseNames() {
201 return this.lowerCaseNames;
202 }
203
204 /***
205 * Sets the flag if parameter names are to be converted to lower case when
206 * name/value pairs are parsed.
207 *
208 * @param b <tt>true</tt> if parameter names are to be
209 * converted to lower case when name/value pairs are parsed.
210 * <tt>false</tt> otherwise.
211 */
212 public void setLowerCaseNames(boolean b) {
213 this.lowerCaseNames = b;
214 }
215
216 /***
217 * Extracts a map of name/value pairs from the given string. Names are
218 * expected to be unique.
219 *
220 * @param str the string that contains a sequence of name/value pairs
221 * @param separator the name/value pairs separator
222 *
223 * @return a map of name/value pairs
224 */
225 public Map parse(final String str, char separator) {
226 if (str == null) {
227 return new HashMap();
228 }
229 return parse(str.toCharArray(), separator);
230 }
231
232 /***
233 * Extracts a map of name/value pairs from the given array of
234 * characters. Names are expected to be unique.
235 *
236 * @param chars the array of characters that contains a sequence of
237 * name/value pairs
238 * @param separator the name/value pairs separator
239 *
240 * @return a map of name/value pairs
241 */
242 public Map parse(final char[] chars, char separator) {
243 if (chars == null) {
244 return new HashMap();
245 }
246 return parse(chars, 0, chars.length, separator);
247 }
248
249 /***
250 * Extracts a map of name/value pairs from the given array of
251 * characters. Names are expected to be unique.
252 *
253 * @param chars the array of characters that contains a sequence of
254 * name/value pairs
255 * @param offset - the initial offset.
256 * @param length - the length.
257 * @param separator the name/value pairs separator
258 *
259 * @return a map of name/value pairs
260 */
261 public Map parse(
262 final char[] chars,
263 int offset,
264 int length,
265 char separator) {
266
267 if (chars == null) {
268 return new HashMap();
269 }
270 HashMap params = new HashMap();
271 this.chars = chars;
272 this.pos = offset;
273 this.len = length;
274
275 String paramName = null;
276 String paramValue = null;
277 while (hasChar()) {
278 paramName = parseToken(new char[] {
279 '=', separator });
280 paramValue = null;
281 if (hasChar() && (chars[pos] == '=')) {
282 pos++;
283 paramValue = parseQuotedToken(new char[] {
284 separator });
285 }
286 if (hasChar() && (chars[pos] == separator)) {
287 pos++;
288 }
289 if ((paramName != null) && (paramName.length() > 0)) {
290 if (this.lowerCaseNames) {
291 paramName = paramName.toLowerCase();
292 }
293 params.put(paramName, paramValue);
294 }
295 }
296 return params;
297 }
298 }