001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.fileupload; 018 019import java.io.UnsupportedEncodingException; 020import java.util.HashMap; 021import java.util.Locale; 022import java.util.Map; 023 024import org.apache.commons.fileupload.util.mime.MimeUtility; 025 026/** 027 * A simple parser intended to parse sequences of name/value pairs. 028 * 029 * Parameter values are expected to be enclosed in quotes if they 030 * contain unsafe characters, such as '=' characters or separators. 031 * Parameter values are optional and can be omitted. 032 * 033 * <p> 034 * <code>param1 = value; param2 = "anything goes; really"; param3</code> 035 * </p> 036 */ 037public class ParameterParser { 038 039 /** 040 * String to be parsed. 041 */ 042 private char[] chars = null; 043 044 /** 045 * Current position in the string. 046 */ 047 private int pos = 0; 048 049 /** 050 * Maximum position in the string. 051 */ 052 private int len = 0; 053 054 /** 055 * Start of a token. 056 */ 057 private int i1 = 0; 058 059 /** 060 * End of a token. 061 */ 062 private int i2 = 0; 063 064 /** 065 * Whether names stored in the map should be converted to lower case. 066 */ 067 private boolean lowerCaseNames = false; 068 069 /** 070 * Default ParameterParser constructor. 071 */ 072 public ParameterParser() { 073 super(); 074 } 075 076 /** 077 * Are there any characters left to parse? 078 * 079 * @return {@code true} if there are unparsed characters, 080 * {@code false} otherwise. 081 */ 082 private boolean hasChar() { 083 return this.pos < this.len; 084 } 085 086 /** 087 * A helper method to process the parsed token. This method removes 088 * leading and trailing blanks as well as enclosing quotation marks, 089 * when necessary. 090 * 091 * @param quoted {@code true} if quotation marks are expected, 092 * {@code false} otherwise. 093 * @return the token 094 */ 095 private String getToken(boolean quoted) { 096 // Trim leading white spaces 097 while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) { 098 i1++; 099 } 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}