View Javadoc

1   /*
2    * Copyright 2001-2004 The Apache Software Foundation
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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          // Trim leading white spaces
93          while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) {
94              i1++;
95          }
96          // Trim trailing white spaces
97          while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) {
98              i2--;
99          }
100         // Strip away quotation marks if necessary
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++; // skip '='
283                 paramValue = parseQuotedToken(new char[] {
284                         separator });
285             }
286             if (hasChar() && (chars[pos] == separator)) {
287                 pos++; // skip separator
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 }