    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     */
    017    package org.apache.commons.fileupload;
    018
    019    import java.util.HashMap;
    020    import java.util.Map;
    021
    022    /**
    023     * A simple parser intended to parse sequences of name/value pairs.
    024     * Parameter values are exptected to be enclosed in quotes if they
    025     * contain unsafe characters, such as '=' characters or separators.
    026     * Parameter values are optional and can be omitted.
    027     *
    028     * <p>
    029     *  <code>param1 = value; param2 = "anything goes; really"; param3</code>
    030     * </p>
    031     *
    032     * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
    033     */
    034
    035    public class ParameterParser {
    036        /**
    037         * String to be parsed.
    038         */
    039        private char[] chars = null;
    040
    041        /**
    042         * Current position in the string.
    043         */
    044        private int pos = 0;
    045
    046        /**
    047         * Maximum position in the string.
    048         */
    049        private int len = 0;
    050
    051        /**
    052         * Start of a token.
    053         */
    054        private int i1 = 0;
    055
    056        /**
    057         * End of a token.
    058         */
    059        private int i2 = 0;
    060
    061        /**
    062         * Whether names stored in the map should be converted to lower case.
    063         */
    064        private boolean lowerCaseNames = false;
    065
    066        /**
    067         * Default ParameterParser constructor.
    068         */
    069        public ParameterParser() {
    070            super();
    071        }
    072
    073        /**
    074         * Are there any characters left to parse?
    075         *
    076         * @return <tt>true</tt> if there are unparsed characters,
    077         *         <tt>false</tt> otherwise.
    078         */
    079        private boolean hasChar() {
    080            return this.pos < this.len;
    081        }
    082
    083        /**
    084         * A helper method to process the parsed token. This method removes
    085         * leading and trailing blanks as well as enclosing quotation marks,
    086         * when necessary.
    087         *
    088         * @param quoted <tt>true</tt> if quotation marks are expected,
    089         *               <tt>false</tt> otherwise.
    090         * @return the token
    091         */
    092        private String getToken(boolean quoted) {
    093            // Trim leading white spaces
    094            while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) {
    095                i1++;
    096            }
    097            // Trim trailing white spaces
    098            while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) {
    099                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    }