##### Coverage Report - org.apache.commons.fileupload.ParameterParser

Classes in this File | Line Coverage | Branch Coverage | Complexity
ParameterParser |
94% | 89/94
90% | 72/80
| 4.5;4,5

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 | 6456 |
         private char[] chars = null;
43 |  |
44 |  |
         /**
45 |  |
          * Current position in the string.
46 |  |
          */
47 | 6456 |
         private int pos = 0;
48 |  |
49 |  |
         /**
50 |  |
          * Maximum position in the string.
51 |  |
          */
52 | 6456 |
         private int len = 0;
53 |  |
54 |  |
         /**
55 |  |
          * Start of a token.
56 |  |
          */
57 | 6456 |
         private int i1 = 0;
58 |  |
59 |  |
         /**
60 |  |
          * End of a token.
61 |  |
          */
62 | 6456 |
         private int i2 = 0;
63 |  |
64 |  |
         /**
65 |  |
          * Whether names stored in the map should be converted to lower case.
66 |  |
          */
67 | 6456 |
         private boolean lowerCaseNames = false;
68 |  |
69 |  |
         /**
70 |  |
          * Default ParameterParser constructor.
71 |  |
          */
72 |  |
         public ParameterParser() {
73 | 6456 |
             super();
74 | 6456 |
         }
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 | 219097 |
             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 | 25909 |
             while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) {
98 | 6508 |
                 i1++;
99 |  |
             }
100 |  |
             // Trim trailing white spaces
101 | 19422 |
             while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) {
102 | 21 |
                 i2--;
103 |  |
             }
104 |  |
             // Strip away quotation marks if necessary
105 | 19401 |
             if (quoted
106 |  |
                 && ((i2 - i1) >= 2)
107 |  |
                 && (chars[i1] == '"')
108 |  |
                 && (chars[i2 - 1] == '"')) {
109 | 6430 |
                 i1++;
110 | 6430 |
                 i2--;
111 |  |
             }
112 | 19401 |
             String result = null;
113 | 19401 |
             if (i2 > i1) {
114 | 19392 |
                 result = new String(chars, i1, i2 - i1);
115 |  |
             }
116 | 19401 |
             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 | 110965 |
             boolean result = false;
130 | 306633 |
             for (char element : charray) {
131 | 208629 |
                 if (ch == element) {
132 | 12961 |
                     result = true;
133 | 12961 |
                     break;
134 |  |
                 }
135 |  |
             }
136 | 110965 |
             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 | 12921 |
             i1 = pos;
151 | 12921 |
             i2 = pos;
152 | 104158 |
             while (hasChar()) {
153 | 104144 |
                 ch = chars[pos];
154 | 104144 |
                 if (isOneOf(ch, terminators)) {
155 | 12907 |
                     break;
156 |  |
                 }
157 | 91237 |
                 i2++;
158 | 91237 |
                 pos++;
159 |  |
             }
160 | 12921 |
             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 | 6480 |
             i1 = pos;
176 | 6480 |
             i2 = pos;
177 | 6480 |
             boolean quoted = false;
178 | 6480 |
             boolean charEscaped = false;
179 | 69736 |
             while (hasChar()) {
180 | 63310 |
                 ch = chars[pos];
181 | 63310 |
                 if (!quoted && isOneOf(ch, terminators)) {
182 | 54 |
                     break;
183 |  |
                 }
184 | 63256 |
                 if (!charEscaped && ch == '"') {
185 | 12862 |
                     quoted = !quoted;
186 |  |
                 }
187 | 63256 |
                 charEscaped = (!charEscaped && ch == '\\');
188 | 63256 |
                 i2++;
189 | 63256 |
                 pos++;
190 |  |
191 |  |
             }
192 | 6480 |
             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 | 0 |
             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 | 6452 |
             this.lowerCaseNames = b;
217 | 6452 |
         }
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 | 45 |
             if (separators == null || separators.length == 0) {
231 | 0 |
                 return new HashMap<String, String>();
232 |  |
             }
233 | 45 |
             char separator = separators[0];
234 | 45 |
             if (str != null) {
235 | 45 |
                 int idx = str.length();
236 | 135 |
                 for (char separator2 : separators) {
237 | 90 |
                     int tmp = str.indexOf(separator2);
238 | 90 |
                     if (tmp != -1 && tmp < idx) {
239 | 44 |
                         idx = tmp;
240 | 44 |
                         separator = separator2;
241 |  |
                     }
242 |  |
                 }
243 |  |
             }
244 | 45 |
             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 | 6464 |
             if (str == null) {
258 | 24 |
                 return new HashMap<String, String>();
259 |  |
             }
260 | 6440 |
             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 | 6440 |
             if (charArray == null) {
275 | 0 |
                 return new HashMap<String, String>();
276 |  |
             }
277 | 6440 |
             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 | 6440 |
             if (charArray == null) {
299 | 0 |
                 return new HashMap<String, String>();
300 |  |
             }
301 | 6440 |
             HashMap<String, String> params = new HashMap<String, String>();
302 | 6440 |
             this.chars = charArray;
303 | 6440 |
             this.pos = offset;
304 | 6440 |
             this.len = length;
305 |  |
306 | 6440 |
             String paramName = null;
307 | 6440 |
             String paramValue = null;
308 | 19361 |
             while (hasChar()) {
309 | 12921 |
                 paramName = parseToken(new char[] {
310 |  |
                         '=', separator });
311 | 12921 |
                 paramValue = null;
312 | 12921 |
                 if (hasChar() && (charArray[pos] == '=')) {
313 | 6480 |
                     pos++; // skip '='
314 | 6480 |
                     paramValue = parseQuotedToken(new char[] {
315 |  |
                             separator });
316 |  |
317 | 6480 |
                     if (paramValue != null) {
318 |  |
                         try {
319 | 6475 |
                             paramValue = MimeUtility.decodeText(paramValue);
320 | 0 |
                         } catch (UnsupportedEncodingException e) {
321 |  |
                             // let's keep the original value in this case
322 | 6475 |
                         }
323 |  |
                     }
324 |  |
                 }
325 | 12921 |
                 if (hasChar() && (charArray[pos] == separator)) {
326 | 6481 |
                     pos++; // skip separator
327 |  |
                 }
328 | 12921 |
                 if ((paramName != null) && (paramName.length() > 0)) {
329 | 12917 |
                     if (this.lowerCaseNames) {
330 | 12892 |
                         paramName = paramName.toLowerCase(Locale.ENGLISH);
331 |  |
                     }
332 |  |
333 | 12917 |
                     params.put(paramName, paramValue);
334 |  |
                 }
335 |  |
             }
336 | 6440 |
             return params;
337 |  |
         }
338 |  |
339 |  |
     }