blob: 02fb42cd4b5cb582227e75a1b5f1de442e851821 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1995-2005 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26/*-
27 * news stream opener
28 */
29
30package sun.net.www;
31
32import java.io.*;
33import java.util.Collections;
34import java.util.Map;
35import java.util.HashMap;
36import java.util.List;
37import java.util.ArrayList;
38import java.util.Set;
39import java.util.Iterator;
40import java.util.NoSuchElementException;
41
42/** An RFC 844 or MIME message header. Includes methods
43 for parsing headers from incoming streams, fetching
44 values, setting values, and printing headers.
45 Key values of null are legal: they indicate lines in
46 the header that don't have a valid key, but do have
47 a value (this isn't legal according to the standard,
48 but lines like this are everywhere). */
49public
50class MessageHeader {
51 private String keys[];
52 private String values[];
53 private int nkeys;
54
55 public MessageHeader () {
56 grow();
57 }
58
59 public MessageHeader (InputStream is) throws java.io.IOException {
60 parseHeader(is);
61 }
62
63 /**
64 * Reset a message header (all key/values removed)
65 */
66 public synchronized void reset() {
67 keys = null;
68 values = null;
69 nkeys = 0;
70 grow();
71 }
72
73 /**
74 * Find the value that corresponds to this key.
75 * It finds only the first occurrence of the key.
76 * @param k the key to find.
77 * @return null if not found.
78 */
79 public synchronized String findValue(String k) {
80 if (k == null) {
81 for (int i = nkeys; --i >= 0;)
82 if (keys[i] == null)
83 return values[i];
84 } else
85 for (int i = nkeys; --i >= 0;) {
86 if (k.equalsIgnoreCase(keys[i]))
87 return values[i];
88 }
89 return null;
90 }
91
92 // return the location of the key
93 public synchronized int getKey(String k) {
94 for (int i = nkeys; --i >= 0;)
95 if ((keys[i] == k) ||
96 (k != null && k.equalsIgnoreCase(keys[i])))
97 return i;
98 return -1;
99 }
100
101 public synchronized String getKey(int n) {
102 if (n < 0 || n >= nkeys) return null;
103 return keys[n];
104 }
105
106 public synchronized String getValue(int n) {
107 if (n < 0 || n >= nkeys) return null;
108 return values[n];
109 }
110
111 /** Deprecated: Use multiValueIterator() instead.
112 *
113 * Find the next value that corresponds to this key.
114 * It finds the first value that follows v. To iterate
115 * over all the values of a key use:
116 * <pre>
117 * for(String v=h.findValue(k); v!=null; v=h.findNextValue(k, v)) {
118 * ...
119 * }
120 * </pre>
121 */
122 public synchronized String findNextValue(String k, String v) {
123 boolean foundV = false;
124 if (k == null) {
125 for (int i = nkeys; --i >= 0;)
126 if (keys[i] == null)
127 if (foundV)
128 return values[i];
129 else if (values[i] == v)
130 foundV = true;
131 } else
132 for (int i = nkeys; --i >= 0;)
133 if (k.equalsIgnoreCase(keys[i]))
134 if (foundV)
135 return values[i];
136 else if (values[i] == v)
137 foundV = true;
138 return null;
139 }
140
141 class HeaderIterator implements Iterator {
142 int index = 0;
143 int next = -1;
144 String key;
145 boolean haveNext = false;
146 Object lock;
147
148 public HeaderIterator (String k, Object lock) {
149 key = k;
150 this.lock = lock;
151 }
152 public boolean hasNext () {
153 synchronized (lock) {
154 if (haveNext) {
155 return true;
156 }
157 while (index < nkeys) {
158 if (key.equalsIgnoreCase (keys[index])) {
159 haveNext = true;
160 next = index++;
161 return true;
162 }
163 index ++;
164 }
165 return false;
166 }
167 }
168 public Object next() {
169 synchronized (lock) {
170 if (haveNext) {
171 haveNext = false;
172 return values [next];
173 }
174 if (hasNext()) {
175 return next();
176 } else {
177 throw new NoSuchElementException ("No more elements");
178 }
179 }
180 }
181 public void remove () {
182 throw new UnsupportedOperationException ("remove not allowed");
183 }
184 }
185
186 /**
187 * return an Iterator that returns all values of a particular
188 * key in sequence
189 */
190 public Iterator multiValueIterator (String k) {
191 return new HeaderIterator (k, this);
192 }
193
194 public synchronized Map getHeaders() {
195 return getHeaders(null);
196 }
197
198 public synchronized Map getHeaders(String[] excludeList) {
199 boolean skipIt = false;
200 Map m = new HashMap();
201 for (int i = nkeys; --i >= 0;) {
202 if (excludeList != null) {
203 // check if the key is in the excludeList.
204 // if so, don't include it in the Map.
205 for (int j = 0; j < excludeList.length; j++) {
206 if ((excludeList[j] != null) &&
207 (excludeList[j].equalsIgnoreCase(keys[i]))) {
208 skipIt = true;
209 break;
210 }
211 }
212 }
213 if (!skipIt) {
214 List l = (List)m.get(keys[i]);
215 if (l == null) {
216 l = new ArrayList();
217 m.put(keys[i], l);
218 }
219 l.add(values[i]);
220 } else {
221 // reset the flag
222 skipIt = false;
223 }
224 }
225
226 Set keySet = m.keySet();
227 for (Iterator i = keySet.iterator(); i.hasNext();) {
228 Object key = i.next();
229 List l = (List)m.get(key);
230 m.put(key, Collections.unmodifiableList(l));
231 }
232
233 return Collections.unmodifiableMap(m);
234 }
235
236 /** Prints the key-value pairs represented by this
237 header. Also prints the RFC required blank line
238 at the end. Omits pairs with a null key. */
239 public synchronized void print(PrintStream p) {
240 for (int i = 0; i < nkeys; i++)
241 if (keys[i] != null) {
242 p.print(keys[i] +
243 (values[i] != null ? ": "+values[i]: "") + "\r\n");
244 }
245 p.print("\r\n");
246 p.flush();
247 }
248
249 /** Adds a key value pair to the end of the
250 header. Duplicates are allowed */
251 public synchronized void add(String k, String v) {
252 grow();
253 keys[nkeys] = k;
254 values[nkeys] = v;
255 nkeys++;
256 }
257
258 /** Prepends a key value pair to the beginning of the
259 header. Duplicates are allowed */
260 public synchronized void prepend(String k, String v) {
261 grow();
262 for (int i = nkeys; i > 0; i--) {
263 keys[i] = keys[i-1];
264 values[i] = values[i-1];
265 }
266 keys[0] = k;
267 values[0] = v;
268 nkeys++;
269 }
270
271 /** Overwrite the previous key/val pair at location 'i'
272 * with the new k/v. If the index didn't exist before
273 * the key/val is simply tacked onto the end.
274 */
275
276 public synchronized void set(int i, String k, String v) {
277 grow();
278 if (i < 0) {
279 return;
280 } else if (i >= nkeys) {
281 add(k, v);
282 } else {
283 keys[i] = k;
284 values[i] = v;
285 }
286 }
287
288
289 /** grow the key/value arrays as needed */
290
291 private void grow() {
292 if (keys == null || nkeys >= keys.length) {
293 String[] nk = new String[nkeys + 4];
294 String[] nv = new String[nkeys + 4];
295 if (keys != null)
296 System.arraycopy(keys, 0, nk, 0, nkeys);
297 if (values != null)
298 System.arraycopy(values, 0, nv, 0, nkeys);
299 keys = nk;
300 values = nv;
301 }
302 }
303
304 /**
305 * Remove the key from the header. If there are multiple values under
306 * the same key, they are all removed.
307 * Nothing is done if the key doesn't exist.
308 * After a remove, the other pairs' order are not changed.
309 * @param k the key to remove
310 */
311 public synchronized void remove(String k) {
312 if(k == null) {
313 for (int i = 0; i < nkeys; i++) {
314 while (keys[i] == null && i < nkeys) {
315 for(int j=i; j<nkeys-1; j++) {
316 keys[j] = keys[j+1];
317 values[j] = values[j+1];
318 }
319 nkeys--;
320 }
321 }
322 } else {
323 for (int i = 0; i < nkeys; i++) {
324 while (k.equalsIgnoreCase(keys[i]) && i < nkeys) {
325 for(int j=i; j<nkeys-1; j++) {
326 keys[j] = keys[j+1];
327 values[j] = values[j+1];
328 }
329 nkeys--;
330 }
331 }
332 }
333 }
334
335 /** Sets the value of a key. If the key already
336 exists in the header, it's value will be
337 changed. Otherwise a new key/value pair will
338 be added to the end of the header. */
339 public synchronized void set(String k, String v) {
340 for (int i = nkeys; --i >= 0;)
341 if (k.equalsIgnoreCase(keys[i])) {
342 values[i] = v;
343 return;
344 }
345 add(k, v);
346 }
347
348 /** Set's the value of a key only if there is no
349 * key with that value already.
350 */
351
352 public synchronized void setIfNotSet(String k, String v) {
353 if (findValue(k) == null) {
354 add(k, v);
355 }
356 }
357
358 /** Convert a message-id string to canonical form (strips off
359 leading and trailing <>s) */
360 public static String canonicalID(String id) {
361 if (id == null)
362 return "";
363 int st = 0;
364 int len = id.length();
365 boolean substr = false;
366 int c;
367 while (st < len && ((c = id.charAt(st)) == '<' ||
368 c <= ' ')) {
369 st++;
370 substr = true;
371 }
372 while (st < len && ((c = id.charAt(len - 1)) == '>' ||
373 c <= ' ')) {
374 len--;
375 substr = true;
376 }
377 return substr ? id.substring(st, len) : id;
378 }
379
380 /** Parse a MIME header from an input stream. */
381 public void parseHeader(InputStream is) throws java.io.IOException {
382 synchronized (this) {
383 nkeys = 0;
384 }
385 mergeHeader(is);
386 }
387
388 /** Parse and merge a MIME header from an input stream. */
389 public void mergeHeader(InputStream is) throws java.io.IOException {
390 if (is == null)
391 return;
392 char s[] = new char[10];
393 int firstc = is.read();
394 while (firstc != '\n' && firstc != '\r' && firstc >= 0) {
395 int len = 0;
396 int keyend = -1;
397 int c;
398 boolean inKey = firstc > ' ';
399 s[len++] = (char) firstc;
400 parseloop:{
401 while ((c = is.read()) >= 0) {
402 switch (c) {
403 case ':':
404 if (inKey && len > 0)
405 keyend = len;
406 inKey = false;
407 break;
408 case '\t':
409 c = ' ';
410 case ' ':
411 inKey = false;
412 break;
413 case '\r':
414 case '\n':
415 firstc = is.read();
416 if (c == '\r' && firstc == '\n') {
417 firstc = is.read();
418 if (firstc == '\r')
419 firstc = is.read();
420 }
421 if (firstc == '\n' || firstc == '\r' || firstc > ' ')
422 break parseloop;
423 /* continuation */
424 c = ' ';
425 break;
426 }
427 if (len >= s.length) {
428 char ns[] = new char[s.length * 2];
429 System.arraycopy(s, 0, ns, 0, len);
430 s = ns;
431 }
432 s[len++] = (char) c;
433 }
434 firstc = -1;
435 }
436 while (len > 0 && s[len - 1] <= ' ')
437 len--;
438 String k;
439 if (keyend <= 0) {
440 k = null;
441 keyend = 0;
442 } else {
443 k = String.copyValueOf(s, 0, keyend);
444 if (keyend < len && s[keyend] == ':')
445 keyend++;
446 while (keyend < len && s[keyend] <= ' ')
447 keyend++;
448 }
449 String v;
450 if (keyend >= len)
451 v = new String();
452 else
453 v = String.copyValueOf(s, keyend, len - keyend);
454 add(k, v);
455 }
456 }
457
458 public synchronized String toString() {
459 String result = super.toString() + nkeys + " pairs: ";
460 for (int i = 0; i < keys.length && i < nkeys; i++) {
461 result += "{"+keys[i]+": "+values[i]+"}";
462 }
463 return result;
464 }
465}