1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
94 while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) {
95 i1++;
96 }
97
98 while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) {
99 i2--;
100 }
101
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.
220 *
221 * @param str the string that contains a sequence of name/value pairs
222 * @param separator the name/value pairs separator
223 *
224 * @return a map of name/value pairs
225 */
226 public Map parse(final String str, char separator) {
227 if (str == null) {
228 return new HashMap();
229 }
230 return parse(str.toCharArray(), separator);
231 }
232
233 /**
234 * Extracts a map of name/value pairs from the given array of
235 * characters. Names are expected to be unique.
236 *
237 * @param chars the array of characters that contains a sequence of
238 * name/value pairs
239 * @param separator the name/value pairs separator
240 *
241 * @return a map of name/value pairs
242 */
243 public Map parse(final char[] chars, char separator) {
244 if (chars == null) {
245 return new HashMap();
246 }
247 return parse(chars, 0, chars.length, separator);
248 }
249
250 /**
251 * Extracts a map of name/value pairs from the given array of
252 * characters. Names are expected to be unique.
253 *
254 * @param chars the array of characters that contains a sequence of
255 * name/value pairs
256 * @param offset - the initial offset.
257 * @param length - the length.
258 * @param separator the name/value pairs separator
259 *
260 * @return a map of name/value pairs
261 */
262 public Map parse(
263 final char[] chars,
264 int offset,
265 int length,
266 char separator) {
267
268 if (chars == null) {
269 return new HashMap();
270 }
271 HashMap params = new HashMap();
272 this.chars = chars;
273 this.pos = offset;
274 this.len = length;
275
276 String paramName = null;
277 String paramValue = null;
278 while (hasChar()) {
279 paramName = parseToken(new char[] {
280 '=', separator });
281 paramValue = null;
282 if (hasChar() && (chars[pos] == '=')) {
283 pos++;
284 paramValue = parseQuotedToken(new char[] {
285 separator });
286 }
287 if (hasChar() && (chars[pos] == separator)) {
288 pos++;
289 }
290 if ((paramName != null) && (paramName.length() > 0)) {
291 if (this.lowerCaseNames) {
292 paramName = paramName.toLowerCase();
293 }
294 params.put(paramName, paramValue);
295 }
296 }
297 return params;
298 }
299 }