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      public HeaderElement[] parseElements(final CharArrayBuffer buffer,
99                                           final ParserCursor cursor) {
100         Args.notNull(buffer, "Char array buffer");
101         Args.notNull(cursor, "Parser cursor");
102         final List<HeaderElement> elements = new ArrayList<HeaderElement>();
103         while (!cursor.atEnd()) {
104             final HeaderElement element = parseHeaderElement(buffer, cursor);
105             if (!(element.getName().length() == 0 && element.getValue() == null)) {
106                 elements.add(element);
107             }
108         }
109         return elements.toArray(new HeaderElement[elements.size()]);
110     }
111 
112 
113     /**
114      * Parses an element with the given parser.
115      *
116      * @param value     the header element to parse
117      * @param parser    the parser to use, or <code>null</code> for default
118      *
119      * @return  the parsed header element
120      */
121     public static
122         HeaderElement parseHeaderElement(final String value,
123                                          final HeaderValueParser parser) throws ParseException {
124         Args.notNull(value, "Value");
125 
126         final CharArrayBuffer buffer = new CharArrayBuffer(value.length());
127         buffer.append(value);
128         final ParserCursor cursor = new ParserCursor(0, value.length());
129         return (parser != null ? parser : BasicHeaderValueParser.INSTANCE)
130                 .parseHeaderElement(buffer, cursor);
131     }
132 
133 
134     // non-javadoc, see interface HeaderValueParser
135     public HeaderElement parseHeaderElement(final CharArrayBuffer buffer,
136                                             final ParserCursor cursor) {
137         Args.notNull(buffer, "Char array buffer");
138         Args.notNull(cursor, "Parser cursor");
139         final NameValuePair nvp = parseNameValuePair(buffer, cursor);
140         NameValuePair[] params = null;
141         if (!cursor.atEnd()) {
142             final char ch = buffer.charAt(cursor.getPos() - 1);
143             if (ch != ELEM_DELIMITER) {
144                 params = parseParameters(buffer, cursor);
145             }
146         }
147         return createHeaderElement(nvp.getName(), nvp.getValue(), params);
148     }
149 
150 
151     /**
152      * Creates a header element.
153      * Called from {@link #parseHeaderElement}.
154      *
155      * @return  a header element representing the argument
156      */
157     protected HeaderElement createHeaderElement(
158             final String name,
159             final String value,
160             final NameValuePair[] params) {
161         return new BasicHeaderElement(name, value, params);
162     }
163 
164 
165     /**
166      * Parses parameters with the given parser.
167      *
168      * @param value     the parameter list to parse
169      * @param parser    the parser to use, or <code>null</code> for default
170      *
171      * @return  array holding the parameters, never <code>null</code>
172      */
173     public static
174         NameValuePair[] parseParameters(final String value,
175                                         final HeaderValueParser parser) throws ParseException {
176         Args.notNull(value, "Value");
177 
178         final CharArrayBuffer buffer = new CharArrayBuffer(value.length());
179         buffer.append(value);
180         final ParserCursor cursor = new ParserCursor(0, value.length());
181         return (parser != null ? parser : BasicHeaderValueParser.INSTANCE)
182                 .parseParameters(buffer, cursor);
183     }
184 
185 
186 
187     // non-javadoc, see interface HeaderValueParser
188     public NameValuePair[] parseParameters(final CharArrayBuffer buffer,
189                                            final ParserCursor cursor) {
190         Args.notNull(buffer, "Char array buffer");
191         Args.notNull(cursor, "Parser cursor");
192         int pos = cursor.getPos();
193         final int indexTo = cursor.getUpperBound();
194 
195         while (pos < indexTo) {
196             final char ch = buffer.charAt(pos);
197             if (HTTP.isWhitespace(ch)) {
198                 pos++;
199             } else {
200                 break;
201             }
202         }
203         cursor.updatePos(pos);
204         if (cursor.atEnd()) {
205             return new NameValuePair[] {};
206         }
207 
208         final List<NameValuePair> params = new ArrayList<NameValuePair>();
209         while (!cursor.atEnd()) {
210             final NameValuePair param = parseNameValuePair(buffer, cursor);
211             params.add(param);
212             final char ch = buffer.charAt(cursor.getPos() - 1);
213             if (ch == ELEM_DELIMITER) {
214                 break;
215             }
216         }
217 
218         return params.toArray(new NameValuePair[params.size()]);
219     }
220 
221     /**
222      * Parses a name-value-pair with the given parser.
223      *
224      * @param value     the NVP to parse
225      * @param parser    the parser to use, or <code>null</code> for default
226      *
227      * @return  the parsed name-value pair
228      */
229     public static
230        NameValuePair parseNameValuePair(final String value,
231                                         final HeaderValueParser parser) throws ParseException {
232         Args.notNull(value, "Value");
233 
234         final CharArrayBuffer buffer = new CharArrayBuffer(value.length());
235         buffer.append(value);
236         final ParserCursor cursor = new ParserCursor(0, value.length());
237         return (parser != null ? parser : BasicHeaderValueParser.INSTANCE)
238                 .parseNameValuePair(buffer, cursor);
239     }
240 
241 
242     // non-javadoc, see interface HeaderValueParser
243     public NameValuePair parseNameValuePair(final CharArrayBuffer buffer,
244                                             final ParserCursor cursor) {
245         return parseNameValuePair(buffer, cursor, ALL_DELIMITERS);
246     }
247 
248     private static boolean isOneOf(final char ch, final char[] chs) {
249         if (chs != null) {
250             for (final char ch2 : chs) {
251                 if (ch == ch2) {
252                     return true;
253                 }
254             }
255         }
256         return false;
257     }
258 
259     public NameValuePair parseNameValuePair(final CharArrayBuffer buffer,
260                                             final ParserCursor cursor,
261                                             final char[] delimiters) {
262         Args.notNull(buffer, "Char array buffer");
263         Args.notNull(cursor, "Parser cursor");
264 
265         boolean terminated = false;
266 
267         int pos = cursor.getPos();
268         final int indexFrom = cursor.getPos();
269         final int indexTo = cursor.getUpperBound();
270 
271         // Find name
272         final String name;
273         while (pos < indexTo) {
274             final char ch = buffer.charAt(pos);
275             if (ch == '=') {
276                 break;
277             }
278             if (isOneOf(ch, delimiters)) {
279                 terminated = true;
280                 break;
281             }
282             pos++;
283         }
284 
285         if (pos == indexTo) {
286             terminated = true;
287             name = buffer.substringTrimmed(indexFrom, indexTo);
288         } else {
289             name = buffer.substringTrimmed(indexFrom, pos);
290             pos++;
291         }
292 
293         if (terminated) {
294             cursor.updatePos(pos);
295             return createNameValuePair(name, null);
296         }
297 
298         // Find value
299         final String value;
300         int i1 = pos;
301 
302         boolean qouted = false;
303         boolean escaped = false;
304         while (pos < indexTo) {
305             final char ch = buffer.charAt(pos);
306             if (ch == '"' && !escaped) {
307                 qouted = !qouted;
308             }
309             if (!qouted && !escaped && isOneOf(ch, delimiters)) {
310                 terminated = true;
311                 break;
312             }
313             if (escaped) {
314                 escaped = false;
315             } else {
316                 escaped = qouted && ch == '\\';
317             }
318             pos++;
319         }
320 
321         int i2 = pos;
322         // Trim leading white spaces
323         while (i1 < i2 && (HTTP.isWhitespace(buffer.charAt(i1)))) {
324             i1++;
325         }
326         // Trim trailing white spaces
327         while ((i2 > i1) && (HTTP.isWhitespace(buffer.charAt(i2 - 1)))) {
328             i2--;
329         }
330         // Strip away quotes if necessary
331         if (((i2 - i1) >= 2)
332             && (buffer.charAt(i1) == '"')
333             && (buffer.charAt(i2 - 1) == '"')) {
334             i1++;
335             i2--;
336         }
337         value = buffer.substring(i1, i2);
338         if (terminated) {
339             pos++;
340         }
341         cursor.updatePos(pos);
342         return createNameValuePair(name, value);
343     }
344 
345     /**
346      * Creates a name-value pair.
347      * Called from {@link #parseNameValuePair}.
348      *
349      * @param name      the name
350      * @param value     the value, or <code>null</code>
351      *
352      * @return  a name-value pair representing the arguments
353      */
354     protected NameValuePair createNameValuePair(final String name, final String value) {
355         return new BasicNameValuePair(name, value);
356     }
357 
358 }
359