|
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 ch.boye.httpclientandroidlib.message; |
|
29 |
|
30 import java.util.List; |
|
31 import java.util.ArrayList; |
|
32 |
|
33 import ch.boye.httpclientandroidlib.HeaderElement; |
|
34 import ch.boye.httpclientandroidlib.NameValuePair; |
|
35 import ch.boye.httpclientandroidlib.ParseException; |
|
36 import ch.boye.httpclientandroidlib.protocol.HTTP; |
|
37 import ch.boye.httpclientandroidlib.util.CharArrayBuffer; |
|
38 |
|
39 /** |
|
40 * Basic implementation for parsing header values into elements. |
|
41 * Instances of this class are stateless and thread-safe. |
|
42 * Derived classes are expected to maintain these properties. |
|
43 * |
|
44 * @since 4.0 |
|
45 */ |
|
46 public class BasicHeaderValueParser implements HeaderValueParser { |
|
47 |
|
48 /** |
|
49 * A default instance of this class, for use as default or fallback. |
|
50 * Note that {@link BasicHeaderValueParser} is not a singleton, there |
|
51 * can be many instances of the class itself and of derived classes. |
|
52 * The instance here provides non-customized, default behavior. |
|
53 */ |
|
54 public final static |
|
55 BasicHeaderValueParser DEFAULT = new BasicHeaderValueParser(); |
|
56 |
|
57 private final static char PARAM_DELIMITER = ';'; |
|
58 private final static char ELEM_DELIMITER = ','; |
|
59 private final static char[] ALL_DELIMITERS = new char[] { |
|
60 PARAM_DELIMITER, |
|
61 ELEM_DELIMITER |
|
62 }; |
|
63 |
|
64 // public default constructor |
|
65 |
|
66 |
|
67 /** |
|
68 * Parses elements with the given parser. |
|
69 * |
|
70 * @param value the header value to parse |
|
71 * @param parser the parser to use, or <code>null</code> for default |
|
72 * |
|
73 * @return array holding the header elements, never <code>null</code> |
|
74 */ |
|
75 public final static |
|
76 HeaderElement[] parseElements(final String value, |
|
77 HeaderValueParser parser) |
|
78 throws ParseException { |
|
79 |
|
80 if (value == null) { |
|
81 throw new IllegalArgumentException |
|
82 ("Value to parse may not be null"); |
|
83 } |
|
84 |
|
85 if (parser == null) |
|
86 parser = BasicHeaderValueParser.DEFAULT; |
|
87 |
|
88 CharArrayBuffer buffer = new CharArrayBuffer(value.length()); |
|
89 buffer.append(value); |
|
90 ParserCursor cursor = new ParserCursor(0, value.length()); |
|
91 return parser.parseElements(buffer, cursor); |
|
92 } |
|
93 |
|
94 |
|
95 // non-javadoc, see interface HeaderValueParser |
|
96 public HeaderElement[] parseElements(final CharArrayBuffer buffer, |
|
97 final ParserCursor cursor) { |
|
98 |
|
99 if (buffer == null) { |
|
100 throw new IllegalArgumentException("Char array buffer may not be null"); |
|
101 } |
|
102 if (cursor == null) { |
|
103 throw new IllegalArgumentException("Parser cursor may not be null"); |
|
104 } |
|
105 |
|
106 List elements = new ArrayList(); |
|
107 while (!cursor.atEnd()) { |
|
108 HeaderElement element = parseHeaderElement(buffer, cursor); |
|
109 if (!(element.getName().length() == 0 && element.getValue() == null)) { |
|
110 elements.add(element); |
|
111 } |
|
112 } |
|
113 return (HeaderElement[]) |
|
114 elements.toArray(new HeaderElement[elements.size()]); |
|
115 } |
|
116 |
|
117 |
|
118 /** |
|
119 * Parses an element with the given parser. |
|
120 * |
|
121 * @param value the header element to parse |
|
122 * @param parser the parser to use, or <code>null</code> for default |
|
123 * |
|
124 * @return the parsed header element |
|
125 */ |
|
126 public final static |
|
127 HeaderElement parseHeaderElement(final String value, |
|
128 HeaderValueParser parser) |
|
129 throws ParseException { |
|
130 |
|
131 if (value == null) { |
|
132 throw new IllegalArgumentException |
|
133 ("Value to parse may not be null"); |
|
134 } |
|
135 |
|
136 if (parser == null) |
|
137 parser = BasicHeaderValueParser.DEFAULT; |
|
138 |
|
139 CharArrayBuffer buffer = new CharArrayBuffer(value.length()); |
|
140 buffer.append(value); |
|
141 ParserCursor cursor = new ParserCursor(0, value.length()); |
|
142 return parser.parseHeaderElement(buffer, cursor); |
|
143 } |
|
144 |
|
145 |
|
146 // non-javadoc, see interface HeaderValueParser |
|
147 public HeaderElement parseHeaderElement(final CharArrayBuffer buffer, |
|
148 final ParserCursor cursor) { |
|
149 |
|
150 if (buffer == null) { |
|
151 throw new IllegalArgumentException("Char array buffer may not be null"); |
|
152 } |
|
153 if (cursor == null) { |
|
154 throw new IllegalArgumentException("Parser cursor may not be null"); |
|
155 } |
|
156 |
|
157 NameValuePair nvp = parseNameValuePair(buffer, cursor); |
|
158 NameValuePair[] params = null; |
|
159 if (!cursor.atEnd()) { |
|
160 char ch = buffer.charAt(cursor.getPos() - 1); |
|
161 if (ch != ELEM_DELIMITER) { |
|
162 params = parseParameters(buffer, cursor); |
|
163 } |
|
164 } |
|
165 return createHeaderElement(nvp.getName(), nvp.getValue(), params); |
|
166 } |
|
167 |
|
168 |
|
169 /** |
|
170 * Creates a header element. |
|
171 * Called from {@link #parseHeaderElement}. |
|
172 * |
|
173 * @return a header element representing the argument |
|
174 */ |
|
175 protected HeaderElement createHeaderElement( |
|
176 final String name, |
|
177 final String value, |
|
178 final NameValuePair[] params) { |
|
179 return new BasicHeaderElement(name, value, params); |
|
180 } |
|
181 |
|
182 |
|
183 /** |
|
184 * Parses parameters with the given parser. |
|
185 * |
|
186 * @param value the parameter list to parse |
|
187 * @param parser the parser to use, or <code>null</code> for default |
|
188 * |
|
189 * @return array holding the parameters, never <code>null</code> |
|
190 */ |
|
191 public final static |
|
192 NameValuePair[] parseParameters(final String value, |
|
193 HeaderValueParser parser) |
|
194 throws ParseException { |
|
195 |
|
196 if (value == null) { |
|
197 throw new IllegalArgumentException |
|
198 ("Value to parse may not be null"); |
|
199 } |
|
200 |
|
201 if (parser == null) |
|
202 parser = BasicHeaderValueParser.DEFAULT; |
|
203 |
|
204 CharArrayBuffer buffer = new CharArrayBuffer(value.length()); |
|
205 buffer.append(value); |
|
206 ParserCursor cursor = new ParserCursor(0, value.length()); |
|
207 return parser.parseParameters(buffer, cursor); |
|
208 } |
|
209 |
|
210 |
|
211 |
|
212 // non-javadoc, see interface HeaderValueParser |
|
213 public NameValuePair[] parseParameters(final CharArrayBuffer buffer, |
|
214 final ParserCursor cursor) { |
|
215 |
|
216 if (buffer == null) { |
|
217 throw new IllegalArgumentException("Char array buffer may not be null"); |
|
218 } |
|
219 if (cursor == null) { |
|
220 throw new IllegalArgumentException("Parser cursor may not be null"); |
|
221 } |
|
222 |
|
223 int pos = cursor.getPos(); |
|
224 int indexTo = cursor.getUpperBound(); |
|
225 |
|
226 while (pos < indexTo) { |
|
227 char ch = buffer.charAt(pos); |
|
228 if (HTTP.isWhitespace(ch)) { |
|
229 pos++; |
|
230 } else { |
|
231 break; |
|
232 } |
|
233 } |
|
234 cursor.updatePos(pos); |
|
235 if (cursor.atEnd()) { |
|
236 return new NameValuePair[] {}; |
|
237 } |
|
238 |
|
239 List params = new ArrayList(); |
|
240 while (!cursor.atEnd()) { |
|
241 NameValuePair param = parseNameValuePair(buffer, cursor); |
|
242 params.add(param); |
|
243 char ch = buffer.charAt(cursor.getPos() - 1); |
|
244 if (ch == ELEM_DELIMITER) { |
|
245 break; |
|
246 } |
|
247 } |
|
248 |
|
249 return (NameValuePair[]) |
|
250 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 |