View Javadoc

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