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. 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 }