View Javadoc

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 }