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.
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++; // skip '='
284                 paramValue = parseQuotedToken(new char[] {
285                         separator });
286             }
287             if (hasChar() && (chars[pos] == separator)) {
288                 pos++; // skip separator
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 }