BasicHeaderValueParser.java revision 069490a5ca2fd1988d29daf45d892f47ad665115
1/*
2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicHeaderValueParser.java $
3 * $Revision: 595670 $
4 * $Date: 2007-11-16 06:15:01 -0800 (Fri, 16 Nov 2007) $
5 *
6 * ====================================================================
7 * Licensed to the Apache Software Foundation (ASF) under one
8 * or more contributor license agreements.  See the NOTICE file
9 * distributed with this work for additional information
10 * regarding copyright ownership.  The ASF licenses this file
11 * to you under the Apache License, Version 2.0 (the
12 * "License"); you may not use this file except in compliance
13 * with the License.  You may obtain a copy of the License at
14 *
15 *   http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing,
18 * software distributed under the License is distributed on an
19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20 * KIND, either express or implied.  See the License for the
21 * specific language governing permissions and limitations
22 * under the License.
23 * ====================================================================
24 *
25 * This software consists of voluntary contributions made by many
26 * individuals on behalf of the Apache Software Foundation.  For more
27 * information on the Apache Software Foundation, please see
28 * <http://www.apache.org/>.
29 *
30 */
31
32package org.apache.http.message;
33
34
35import java.util.List;
36import java.util.ArrayList;
37
38import org.apache.http.HeaderElement;
39import org.apache.http.NameValuePair;
40import org.apache.http.ParseException;
41import org.apache.http.protocol.HTTP;
42import org.apache.http.util.CharArrayBuffer;
43
44
45
46/**
47 * Basic implementation for parsing header values into elements.
48 * Instances of this class are stateless and thread-safe.
49 * Derived classes are expected to maintain these properties.
50 *
51 * @author <a href="mailto:bcholmes@interlog.com">B.C. Holmes</a>
52 * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
53 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
54 * @author <a href="mailto:oleg at ural.com">Oleg Kalnichevski</a>
55 * @author and others
56 *
57 *
58 * <!-- empty lines above to avoid 'svn diff' context problems -->
59 * @version $Revision: 595670 $
60 *
61 * @since 4.0
62 */
63public class BasicHeaderValueParser implements HeaderValueParser {
64
65    /**
66     * A default instance of this class, for use as default or fallback.
67     * Note that {@link BasicHeaderValueParser} is not a singleton, there
68     * can be many instances of the class itself and of derived classes.
69     * The instance here provides non-customized, default behavior.
70     */
71    public final static
72        BasicHeaderValueParser DEFAULT = new BasicHeaderValueParser();
73
74    private final static char PARAM_DELIMITER                = ';';
75    private final static char ELEM_DELIMITER                 = ',';
76    private final static char[] ALL_DELIMITERS               = new char[] {
77                                                                PARAM_DELIMITER,
78                                                                ELEM_DELIMITER
79                                                                };
80
81    // public default constructor
82
83
84    /**
85     * Parses elements with the given parser.
86     *
87     * @param value     the header value to parse
88     * @param parser    the parser to use, or <code>null</code> for default
89     *
90     * @return  array holding the header elements, never <code>null</code>
91     */
92    public final static
93        HeaderElement[] parseElements(final String value,
94                                      HeaderValueParser parser)
95        throws ParseException {
96
97        if (value == null) {
98            throw new IllegalArgumentException
99                ("Value to parse may not be null");
100        }
101
102        if (parser == null)
103            parser = BasicHeaderValueParser.DEFAULT;
104
105        CharArrayBuffer buffer = new CharArrayBuffer(value.length());
106        buffer.append(value);
107        ParserCursor cursor = new ParserCursor(0, value.length());
108        return parser.parseElements(buffer, cursor);
109    }
110
111
112    // non-javadoc, see interface HeaderValueParser
113    public HeaderElement[] parseElements(final CharArrayBuffer buffer,
114                                         final ParserCursor cursor) {
115
116        if (buffer == null) {
117            throw new IllegalArgumentException("Char array buffer may not be null");
118        }
119        if (cursor == null) {
120            throw new IllegalArgumentException("Parser cursor may not be null");
121        }
122
123        List elements = new ArrayList();
124        while (!cursor.atEnd()) {
125            HeaderElement element = parseHeaderElement(buffer, cursor);
126            if (!(element.getName().length() == 0 && element.getValue() == null)) {
127                elements.add(element);
128            }
129        }
130        return (HeaderElement[])
131            elements.toArray(new HeaderElement[elements.size()]);
132    }
133
134
135    /**
136     * Parses an element with the given parser.
137     *
138     * @param value     the header element to parse
139     * @param parser    the parser to use, or <code>null</code> for default
140     *
141     * @return  the parsed header element
142     */
143    public final static
144        HeaderElement parseHeaderElement(final String value,
145                                         HeaderValueParser parser)
146        throws ParseException {
147
148        if (value == null) {
149            throw new IllegalArgumentException
150                ("Value to parse may not be null");
151        }
152
153        if (parser == null)
154            parser = BasicHeaderValueParser.DEFAULT;
155
156        CharArrayBuffer buffer = new CharArrayBuffer(value.length());
157        buffer.append(value);
158        ParserCursor cursor = new ParserCursor(0, value.length());
159        return parser.parseHeaderElement(buffer, cursor);
160    }
161
162
163    // non-javadoc, see interface HeaderValueParser
164    public HeaderElement parseHeaderElement(final CharArrayBuffer buffer,
165                                            final ParserCursor cursor) {
166
167        if (buffer == null) {
168            throw new IllegalArgumentException("Char array buffer may not be null");
169        }
170        if (cursor == null) {
171            throw new IllegalArgumentException("Parser cursor may not be null");
172        }
173
174        NameValuePair nvp = parseNameValuePair(buffer, cursor);
175        NameValuePair[] params = null;
176        if (!cursor.atEnd()) {
177            char ch = buffer.charAt(cursor.getPos() - 1);
178            if (ch != ELEM_DELIMITER) {
179                params = parseParameters(buffer, cursor);
180            }
181        }
182        return createHeaderElement(nvp.getName(), nvp.getValue(), params);
183    }
184
185
186    /**
187     * Creates a header element.
188     * Called from {@link #parseHeaderElement}.
189     *
190     * @return  a header element representing the argument
191     */
192    protected HeaderElement createHeaderElement(
193            final String name,
194            final String value,
195            final NameValuePair[] params) {
196        return new BasicHeaderElement(name, value, params);
197    }
198
199
200    /**
201     * Parses parameters with the given parser.
202     *
203     * @param value     the parameter list to parse
204     * @param parser    the parser to use, or <code>null</code> for default
205     *
206     * @return  array holding the parameters, never <code>null</code>
207     */
208    public final static
209        NameValuePair[] parseParameters(final String value,
210                                        HeaderValueParser parser)
211        throws ParseException {
212
213        if (value == null) {
214            throw new IllegalArgumentException
215                ("Value to parse may not be null");
216        }
217
218        if (parser == null)
219            parser = BasicHeaderValueParser.DEFAULT;
220
221        CharArrayBuffer buffer = new CharArrayBuffer(value.length());
222        buffer.append(value);
223        ParserCursor cursor = new ParserCursor(0, value.length());
224        return parser.parseParameters(buffer, cursor);
225    }
226
227
228
229    // non-javadoc, see interface HeaderValueParser
230    public NameValuePair[] parseParameters(final CharArrayBuffer buffer,
231                                           final ParserCursor cursor) {
232
233        if (buffer == null) {
234            throw new IllegalArgumentException("Char array buffer may not be null");
235        }
236        if (cursor == null) {
237            throw new IllegalArgumentException("Parser cursor may not be null");
238        }
239
240        int pos = cursor.getPos();
241        int indexTo = cursor.getUpperBound();
242
243        while (pos < indexTo) {
244            char ch = buffer.charAt(pos);
245            if (HTTP.isWhitespace(ch)) {
246                pos++;
247            } else {
248                break;
249            }
250        }
251        cursor.updatePos(pos);
252        if (cursor.atEnd()) {
253            return new NameValuePair[] {};
254        }
255
256        List params = new ArrayList();
257        while (!cursor.atEnd()) {
258            NameValuePair param = parseNameValuePair(buffer, cursor);
259            params.add(param);
260            char ch = buffer.charAt(cursor.getPos() - 1);
261            if (ch == ELEM_DELIMITER) {
262                break;
263            }
264        }
265
266        return (NameValuePair[])
267            params.toArray(new NameValuePair[params.size()]);
268    }
269
270    /**
271     * Parses a name-value-pair with the given parser.
272     *
273     * @param value     the NVP to parse
274     * @param parser    the parser to use, or <code>null</code> for default
275     *
276     * @return  the parsed name-value pair
277     */
278    public final static
279       NameValuePair parseNameValuePair(final String value,
280                                        HeaderValueParser parser)
281        throws ParseException {
282
283        if (value == null) {
284            throw new IllegalArgumentException
285                ("Value to parse may not be null");
286        }
287
288        if (parser == null)
289            parser = BasicHeaderValueParser.DEFAULT;
290
291        CharArrayBuffer buffer = new CharArrayBuffer(value.length());
292        buffer.append(value);
293        ParserCursor cursor = new ParserCursor(0, value.length());
294        return parser.parseNameValuePair(buffer, cursor);
295    }
296
297
298    // non-javadoc, see interface HeaderValueParser
299    public NameValuePair parseNameValuePair(final CharArrayBuffer buffer,
300                                            final ParserCursor cursor) {
301        return parseNameValuePair(buffer, cursor, ALL_DELIMITERS);
302    }
303
304    private static boolean isOneOf(final char ch, final char[] chs) {
305        if (chs != null) {
306            for (int i = 0; i < chs.length; i++) {
307                if (ch == chs[i]) {
308                    return true;
309                }
310            }
311        }
312        return false;
313    }
314
315    public NameValuePair parseNameValuePair(final CharArrayBuffer buffer,
316                                            final ParserCursor cursor,
317                                            final char[] delimiters) {
318
319        if (buffer == null) {
320            throw new IllegalArgumentException("Char array buffer may not be null");
321        }
322        if (cursor == null) {
323            throw new IllegalArgumentException("Parser cursor may not be null");
324        }
325
326        boolean terminated = false;
327
328        int pos = cursor.getPos();
329        int indexFrom = cursor.getPos();
330        int indexTo = cursor.getUpperBound();
331
332        // Find name
333        String name = null;
334        while (pos < indexTo) {
335            char ch = buffer.charAt(pos);
336            if (ch == '=') {
337                break;
338            }
339            if (isOneOf(ch, delimiters)) {
340                terminated = true;
341                break;
342            }
343            pos++;
344        }
345
346        if (pos == indexTo) {
347            terminated = true;
348            name = buffer.substringTrimmed(indexFrom, indexTo);
349        } else {
350            name = buffer.substringTrimmed(indexFrom, pos);
351            pos++;
352        }
353
354        if (terminated) {
355            cursor.updatePos(pos);
356            return createNameValuePair(name, null);
357        }
358
359        // Find value
360        String value = null;
361        int i1 = pos;
362
363        boolean qouted = false;
364        boolean escaped = false;
365        while (pos < indexTo) {
366            char ch = buffer.charAt(pos);
367            if (ch == '"' && !escaped) {
368                qouted = !qouted;
369            }
370            if (!qouted && !escaped && isOneOf(ch, delimiters)) {
371                terminated = true;
372                break;
373            }
374            if (escaped) {
375                escaped = false;
376            } else {
377                escaped = qouted && ch == '\\';
378            }
379            pos++;
380        }
381
382        int i2 = pos;
383        // Trim leading white spaces
384        while (i1 < i2 && (HTTP.isWhitespace(buffer.charAt(i1)))) {
385            i1++;
386        }
387        // Trim trailing white spaces
388        while ((i2 > i1) && (HTTP.isWhitespace(buffer.charAt(i2 - 1)))) {
389            i2--;
390        }
391        // Strip away quotes if necessary
392        if (((i2 - i1) >= 2)
393            && (buffer.charAt(i1) == '"')
394            && (buffer.charAt(i2 - 1) == '"')) {
395            i1++;
396            i2--;
397        }
398        value = buffer.substring(i1, i2);
399        if (terminated) {
400            pos++;
401        }
402        cursor.updatePos(pos);
403        return createNameValuePair(name, value);
404    }
405
406    /**
407     * Creates a name-value pair.
408     * Called from {@link #parseNameValuePair}.
409     *
410     * @param name      the name
411     * @param value     the value, or <code>null</code>
412     *
413     * @return  a name-value pair representing the arguments
414     */
415    protected NameValuePair createNameValuePair(final String name, final String value) {
416        return new BasicNameValuePair(name, value);
417    }
418
419}
420
421