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    }