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.List;
31  import java.util.ArrayList;
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.CharArrayBuffer;
39  
40  /**
41   * Basic implementation for parsing header values into elements.
42   * Instances of this class are stateless and thread-safe.
43   * Derived classes are expected to maintain these properties.
44   *
45   * @since 4.0
46   */
47  @Immutable
48  public class BasicHeaderValueParser implements HeaderValueParser {
49  
50      /**
51       * A default instance of this class, for use as default or fallback.
52       * Note that {@link BasicHeaderValueParser} is not a singleton, there
53       * can be many instances of the class itself and of derived classes.
54       * The instance here provides non-customized, default behavior.
55       */
56      public final static
57          BasicHeaderValueParser DEFAULT = new BasicHeaderValueParser();
58  
59      private final static char PARAM_DELIMITER                = ';';
60      private final static char ELEM_DELIMITER                 = ',';
61      private final static char[] ALL_DELIMITERS               = new char[] {
62                                                                  PARAM_DELIMITER,
63                                                                  ELEM_DELIMITER
64                                                                  };
65  
66      // public default constructor
67  
68  
69      /**
70       * Parses elements with the given parser.
71       *
72       * @param value     the header value to parse
73       * @param parser    the parser to use, or <code>null</code> for default
74       *
75       * @return  array holding the header elements, never <code>null</code>
76       */
77      public final static
78          HeaderElement[] parseElements(final String value,
79                                        HeaderValueParser parser)
80          throws ParseException {
81  
82          if (value == null) {
83              throw new IllegalArgumentException
84                  ("Value to parse may not be null");
85          }
86  
87          if (parser == null)
88              parser = BasicHeaderValueParser.DEFAULT;
89  
90          CharArrayBuffer buffer = new CharArrayBuffer(value.length());
91          buffer.append(value);
92          ParserCursor cursor = new ParserCursor(0, value.length());
93          return parser.parseElements(buffer, cursor);
94      }
95  
96  
97      // non-javadoc, see interface HeaderValueParser
98      public HeaderElement[] parseElements(final CharArrayBuffer buffer,
99                                           final ParserCursor cursor) {
100 
101         if (buffer == null) {
102             throw new IllegalArgumentException("Char array buffer may not be null");
103         }
104         if (cursor == null) {
105             throw new IllegalArgumentException("Parser cursor may not be null");
106         }
107 
108         List<HeaderElement> elements = new ArrayList<HeaderElement>();
109         while (!cursor.atEnd()) {
110             HeaderElement element = parseHeaderElement(buffer, cursor);
111             if (!(element.getName().length() == 0 && element.getValue() == null)) {
112                 elements.add(element);
113             }
114         }
115         return elements.toArray(new HeaderElement[elements.size()]);
116     }
117 
118 
119     /**
120      * Parses an element with the given parser.
121      *
122      * @param value     the header element to parse
123      * @param parser    the parser to use, or <code>null</code> for default
124      *
125      * @return  the parsed header element
126      */
127     public final static
128         HeaderElement parseHeaderElement(final String value,
129                                          HeaderValueParser parser)
130         throws ParseException {
131 
132         if (value == null) {
133             throw new IllegalArgumentException
134                 ("Value to parse may not be null");
135         }
136 
137         if (parser == null)
138             parser = BasicHeaderValueParser.DEFAULT;
139 
140         CharArrayBuffer buffer = new CharArrayBuffer(value.length());
141         buffer.append(value);
142         ParserCursor cursor = new ParserCursor(0, value.length());
143         return parser.parseHeaderElement(buffer, cursor);
144     }
145 
146 
147     // non-javadoc, see interface HeaderValueParser
148     public HeaderElement parseHeaderElement(final CharArrayBuffer buffer,
149                                             final ParserCursor cursor) {
150 
151         if (buffer == null) {
152             throw new IllegalArgumentException("Char array buffer may not be null");
153         }
154         if (cursor == null) {
155             throw new IllegalArgumentException("Parser cursor may not be null");
156         }
157 
158         NameValuePair nvp = parseNameValuePair(buffer, cursor);
159         NameValuePair[] params = null;
160         if (!cursor.atEnd()) {
161             char ch = buffer.charAt(cursor.getPos() - 1);
162             if (ch != ELEM_DELIMITER) {
163                 params = parseParameters(buffer, cursor);
164             }
165         }
166         return createHeaderElement(nvp.getName(), nvp.getValue(), params);
167     }
168 
169 
170     /**
171      * Creates a header element.
172      * Called from {@link #parseHeaderElement}.
173      *
174      * @return  a header element representing the argument
175      */
176     protected HeaderElement createHeaderElement(
177             final String name,
178             final String value,
179             final NameValuePair[] params) {
180         return new BasicHeaderElement(name, value, params);
181     }
182 
183 
184     /**
185      * Parses parameters with the given parser.
186      *
187      * @param value     the parameter list to parse
188      * @param parser    the parser to use, or <code>null</code> for default
189      *
190      * @return  array holding the parameters, never <code>null</code>
191      */
192     public final static
193         NameValuePair[] parseParameters(final String value,
194                                         HeaderValueParser parser)
195         throws ParseException {
196 
197         if (value == null) {
198             throw new IllegalArgumentException
199                 ("Value to parse may not be null");
200         }
201 
202         if (parser == null)
203             parser = BasicHeaderValueParser.DEFAULT;
204 
205         CharArrayBuffer buffer = new CharArrayBuffer(value.length());
206         buffer.append(value);
207         ParserCursor cursor = new ParserCursor(0, value.length());
208         return parser.parseParameters(buffer, cursor);
209     }
210 
211 
212 
213     // non-javadoc, see interface HeaderValueParser
214     public NameValuePair[] parseParameters(final CharArrayBuffer buffer,
215                                            final ParserCursor cursor) {
216 
217         if (buffer == null) {
218             throw new IllegalArgumentException("Char array buffer may not be null");
219         }
220         if (cursor == null) {
221             throw new IllegalArgumentException("Parser cursor may not be null");
222         }
223 
224         int pos = cursor.getPos();
225         int indexTo = cursor.getUpperBound();
226 
227         while (pos < indexTo) {
228             char ch = buffer.charAt(pos);
229             if (HTTP.isWhitespace(ch)) {
230                 pos++;
231             } else {
232                 break;
233             }
234         }
235         cursor.updatePos(pos);
236         if (cursor.atEnd()) {
237             return new NameValuePair[] {};
238         }
239 
240         List<NameValuePair> params = new ArrayList<NameValuePair>();
241         while (!cursor.atEnd()) {
242             NameValuePair param = parseNameValuePair(buffer, cursor);
243             params.add(param);
244             char ch = buffer.charAt(cursor.getPos() - 1);
245             if (ch == ELEM_DELIMITER) {
246                 break;
247             }
248         }
249 
250         return params.toArray(new NameValuePair[params.size()]);
251     }
252 
253     /**
254      * Parses a name-value-pair with the given parser.
255      *
256      * @param value     the NVP to parse
257      * @param parser    the parser to use, or <code>null</code> for default
258      *
259      * @return  the parsed name-value pair
260      */
261     public final static
262        NameValuePair parseNameValuePair(final String value,
263                                         HeaderValueParser parser)
264         throws ParseException {
265 
266         if (value == null) {
267             throw new IllegalArgumentException
268                 ("Value to parse may not be null");
269         }
270 
271         if (parser == null)
272             parser = BasicHeaderValueParser.DEFAULT;
273 
274         CharArrayBuffer buffer = new CharArrayBuffer(value.length());
275         buffer.append(value);
276         ParserCursor cursor = new ParserCursor(0, value.length());
277         return parser.parseNameValuePair(buffer, cursor);
278     }
279 
280 
281     // non-javadoc, see interface HeaderValueParser
282     public NameValuePair parseNameValuePair(final CharArrayBuffer buffer,
283                                             final ParserCursor cursor) {
284         return parseNameValuePair(buffer, cursor, ALL_DELIMITERS);
285     }
286 
287     private static boolean isOneOf(final char ch, final char[] chs) {
288         if (chs != null) {
289             for (int i = 0; i < chs.length; i++) {
290                 if (ch == chs[i]) {
291                     return true;
292                 }
293             }
294         }
295         return false;
296     }
297 
298     public NameValuePair parseNameValuePair(final CharArrayBuffer buffer,
299                                             final ParserCursor cursor,
300                                             final char[] delimiters) {
301 
302         if (buffer == null) {
303             throw new IllegalArgumentException("Char array buffer may not be null");
304         }
305         if (cursor == null) {
306             throw new IllegalArgumentException("Parser cursor may not be null");
307         }
308 
309         boolean terminated = false;
310 
311         int pos = cursor.getPos();
312         int indexFrom = cursor.getPos();
313         int indexTo = cursor.getUpperBound();
314 
315         // Find name
316         String name = null;
317         while (pos < indexTo) {
318             char ch = buffer.charAt(pos);
319             if (ch == '=') {
320                 break;
321             }
322             if (isOneOf(ch, delimiters)) {
323                 terminated = true;
324                 break;
325             }
326             pos++;
327         }
328 
329         if (pos == indexTo) {
330             terminated = true;
331             name = buffer.substringTrimmed(indexFrom, indexTo);
332         } else {
333             name = buffer.substringTrimmed(indexFrom, pos);
334             pos++;
335         }
336 
337         if (terminated) {
338             cursor.updatePos(pos);
339             return createNameValuePair(name, null);
340         }
341 
342         // Find value
343         String value = null;
344         int i1 = pos;
345 
346         boolean qouted = false;
347         boolean escaped = false;
348         while (pos < indexTo) {
349             char ch = buffer.charAt(pos);
350             if (ch == '"' && !escaped) {
351                 qouted = !qouted;
352             }
353             if (!qouted && !escaped && isOneOf(ch, delimiters)) {
354                 terminated = true;
355                 break;
356             }
357             if (escaped) {
358                 escaped = false;
359             } else {
360                 escaped = qouted && ch == '\\';
361             }
362             pos++;
363         }
364 
365         int i2 = pos;
366         // Trim leading white spaces
367         while (i1 < i2 && (HTTP.isWhitespace(buffer.charAt(i1)))) {
368             i1++;
369         }
370         // Trim trailing white spaces
371         while ((i2 > i1) && (HTTP.isWhitespace(buffer.charAt(i2 - 1)))) {
372             i2--;
373         }
374         // Strip away quotes if necessary
375         if (((i2 - i1) >= 2)
376             && (buffer.charAt(i1) == '"')
377             && (buffer.charAt(i2 - 1) == '"')) {
378             i1++;
379             i2--;
380         }
381         value = buffer.substring(i1, i2);
382         if (terminated) {
383             pos++;
384         }
385         cursor.updatePos(pos);
386         return createNameValuePair(name, value);
387     }
388 
389     /**
390      * Creates a name-value pair.
391      * Called from {@link #parseNameValuePair}.
392      *
393      * @param name      the name
394      * @param value     the value, or <code>null</code>
395      *
396      * @return  a name-value pair representing the arguments
397      */
398     protected NameValuePair createNameValuePair(final String name, final String value) {
399         return new BasicNameValuePair(name, value);
400     }
401 
402 }
403