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 }