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 }