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.io.UnsupportedEncodingException;
20  import java.util.HashMap;
21  import java.util.Locale;
22  import java.util.Map;
23  
24  import org.apache.commons.fileupload.util.mime.MimeUtility;
25  
26  /**
27   * A simple parser intended to parse sequences of name/value pairs.
28   *
29   * Parameter values are expected to be enclosed in quotes if they
30   * contain unsafe characters, such as '=' characters or separators.
31   * Parameter values are optional and can be omitted.
32   *
33   * <p>
34   *  <code>param1 = value; param2 = "anything goes; really"; param3</code>
35   * </p>
36   */
37  public class ParameterParser {
38  
39      /**
40       * String to be parsed.
41       */
42      private char[] chars = null;
43  
44      /**
45       * Current position in the string.
46       */
47      private int pos = 0;
48  
49      /**
50       * Maximum position in the string.
51       */
52      private int len = 0;
53  
54      /**
55       * Start of a token.
56       */
57      private int i1 = 0;
58  
59      /**
60       * End of a token.
61       */
62      private int i2 = 0;
63  
64      /**
65       * Whether names stored in the map should be converted to lower case.
66       */
67      private boolean lowerCaseNames = false;
68  
69      /**
70       * Default ParameterParser constructor.
71       */
72      public ParameterParser() {
73          super();
74      }
75  
76      /**
77       * Are there any characters left to parse?
78       *
79       * @return {@code true} if there are unparsed characters,
80       *         {@code false} otherwise.
81       */
82      private boolean hasChar() {
83          return this.pos < this.len;
84      }
85  
86      /**
87       * A helper method to process the parsed token. This method removes
88       * leading and trailing blanks as well as enclosing quotation marks,
89       * when necessary.
90       *
91       * @param quoted {@code true} if quotation marks are expected,
92       *               {@code false} otherwise.
93       * @return the token
94       */
95      private String getToken(boolean quoted) {
96          // Trim leading white spaces
97          while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) {
98              i1++;
99          }
100         // Trim trailing white spaces
101         while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) {
102             i2--;
103         }
104         // Strip away quotation marks if necessary
105         if (quoted
106             && ((i2 - i1) >= 2)
107             && (chars[i1] == '"')
108             && (chars[i2 - 1] == '"')) {
109             i1++;
110             i2--;
111         }
112         String result = null;
113         if (i2 > i1) {
114             result = new String(chars, i1, i2 - i1);
115         }
116         return result;
117     }
118 
119     /**
120      * Tests if the given character is present in the array of characters.
121      *
122      * @param ch the character to test for presense in the array of characters
123      * @param charray the array of characters to test against
124      *
125      * @return {@code true} if the character is present in the array of
126      *   characters, {@code false} otherwise.
127      */
128     private boolean isOneOf(char ch, final char[] charray) {
129         boolean result = false;
130         for (char element : charray) {
131             if (ch == element) {
132                 result = true;
133                 break;
134             }
135         }
136         return result;
137     }
138 
139     /**
140      * Parses out a token until any of the given terminators
141      * is encountered.
142      *
143      * @param terminators the array of terminating characters. Any of these
144      * characters when encountered signify the end of the token
145      *
146      * @return the token
147      */
148     private String parseToken(final char[] terminators) {
149         char ch;
150         i1 = pos;
151         i2 = pos;
152         while (hasChar()) {
153             ch = chars[pos];
154             if (isOneOf(ch, terminators)) {
155                 break;
156             }
157             i2++;
158             pos++;
159         }
160         return getToken(false);
161     }
162 
163     /**
164      * Parses out a token until any of the given terminators
165      * is encountered outside the quotation marks.
166      *
167      * @param terminators the array of terminating characters. Any of these
168      * characters when encountered outside the quotation marks signify the end
169      * of the token
170      *
171      * @return the token
172      */
173     private String parseQuotedToken(final char[] terminators) {
174         char ch;
175         i1 = pos;
176         i2 = pos;
177         boolean quoted = false;
178         boolean charEscaped = false;
179         while (hasChar()) {
180             ch = chars[pos];
181             if (!quoted && isOneOf(ch, terminators)) {
182                 break;
183             }
184             if (!charEscaped && ch == '"') {
185                 quoted = !quoted;
186             }
187             charEscaped = (!charEscaped && ch == '\\');
188             i2++;
189             pos++;
190 
191         }
192         return getToken(true);
193     }
194 
195     /**
196      * Returns {@code true} if parameter names are to be converted to lower
197      * case when name/value pairs are parsed.
198      *
199      * @return {@code true} if parameter names are to be
200      * converted to lower case when name/value pairs are parsed.
201      * Otherwise returns {@code false}
202      */
203     public boolean isLowerCaseNames() {
204         return this.lowerCaseNames;
205     }
206 
207     /**
208      * Sets the flag if parameter names are to be converted to lower case when
209      * name/value pairs are parsed.
210      *
211      * @param b {@code true} if parameter names are to be
212      * converted to lower case when name/value pairs are parsed.
213      * {@code false} otherwise.
214      */
215     public void setLowerCaseNames(boolean b) {
216         this.lowerCaseNames = b;
217     }
218 
219     /**
220      * Extracts a map of name/value pairs from the given string. Names are
221      * expected to be unique. Multiple separators may be specified and
222      * the earliest found in the input string is used.
223      *
224      * @param str the string that contains a sequence of name/value pairs
225      * @param separators the name/value pairs separators
226      *
227      * @return a map of name/value pairs
228      */
229     public Map<String, String> parse(final String str, char[] separators) {
230         if (separators == null || separators.length == 0) {
231             return new HashMap<String, String>();
232         }
233         char separator = separators[0];
234         if (str != null) {
235             int idx = str.length();
236             for (char separator2 : separators) {
237                 int tmp = str.indexOf(separator2);
238                 if (tmp != -1 && tmp < idx) {
239                     idx = tmp;
240                     separator = separator2;
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<String, String> parse(final String str, char separator) {
257         if (str == null) {
258             return new HashMap<String, String>();
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 charArray 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<String, String> parse(final char[] charArray, char separator) {
274         if (charArray == null) {
275             return new HashMap<String, String>();
276         }
277         return parse(charArray, 0, charArray.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 charArray 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<String, String> parse(
293         final char[] charArray,
294         int offset,
295         int length,
296         char separator) {
297 
298         if (charArray == null) {
299             return new HashMap<String, String>();
300         }
301         HashMap<String, String> params = new HashMap<String, String>();
302         this.chars = charArray;
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() && (charArray[pos] == '=')) {
313                 pos++; // skip '='
314                 paramValue = parseQuotedToken(new char[] {
315                         separator });
316 
317                 if (paramValue != null) {
318                     try {
319                         paramValue = MimeUtility.decodeText(paramValue);
320                     } catch (UnsupportedEncodingException e) {
321                         // let's keep the original value in this case
322                     }
323                 }
324             }
325             if (hasChar() && (charArray[pos] == separator)) {
326                 pos++; // skip separator
327             }
328             if ((paramName != null) && (paramName.length() > 0)) {
329                 if (this.lowerCaseNames) {
330                     paramName = paramName.toLowerCase(Locale.ENGLISH);
331                 }
332 
333                 params.put(paramName, paramValue);
334             }
335         }
336         return params;
337     }
338 
339 }