001: /*
002: * Copyright 1995-2005 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: /*-
027: * news stream opener
028: */
029:
030: package sun.net.www;
031:
032: import java.io.*;
033: import java.util.Collections;
034: import java.util.Map;
035: import java.util.HashMap;
036: import java.util.List;
037: import java.util.ArrayList;
038: import java.util.Set;
039: import java.util.Iterator;
040: import java.util.NoSuchElementException;
041:
042: /** An RFC 844 or MIME message header. Includes methods
043: for parsing headers from incoming streams, fetching
044: values, setting values, and printing headers.
045: Key values of null are legal: they indicate lines in
046: the header that don't have a valid key, but do have
047: a value (this isn't legal according to the standard,
048: but lines like this are everywhere). */
049: public class MessageHeader {
050: private String keys[];
051: private String values[];
052: private int nkeys;
053:
054: public MessageHeader() {
055: grow();
056: }
057:
058: public MessageHeader(InputStream is) throws java.io.IOException {
059: parseHeader(is);
060: }
061:
062: /**
063: * Reset a message header (all key/values removed)
064: */
065: public synchronized void reset() {
066: keys = null;
067: values = null;
068: nkeys = 0;
069: grow();
070: }
071:
072: /**
073: * Find the value that corresponds to this key.
074: * It finds only the first occurrence of the key.
075: * @param k the key to find.
076: * @return null if not found.
077: */
078: public synchronized String findValue(String k) {
079: if (k == null) {
080: for (int i = nkeys; --i >= 0;)
081: if (keys[i] == null)
082: return values[i];
083: } else
084: for (int i = nkeys; --i >= 0;) {
085: if (k.equalsIgnoreCase(keys[i]))
086: return values[i];
087: }
088: return null;
089: }
090:
091: // return the location of the key
092: public synchronized int getKey(String k) {
093: for (int i = nkeys; --i >= 0;)
094: if ((keys[i] == k)
095: || (k != null && k.equalsIgnoreCase(keys[i])))
096: return i;
097: return -1;
098: }
099:
100: public synchronized String getKey(int n) {
101: if (n < 0 || n >= nkeys)
102: return null;
103: return keys[n];
104: }
105:
106: public synchronized String getValue(int n) {
107: if (n < 0 || n >= nkeys)
108: return null;
109: return values[n];
110: }
111:
112: /** Deprecated: Use multiValueIterator() instead.
113: *
114: * Find the next value that corresponds to this key.
115: * It finds the first value that follows v. To iterate
116: * over all the values of a key use:
117: * <pre>
118: * for(String v=h.findValue(k); v!=null; v=h.findNextValue(k, v)) {
119: * ...
120: * }
121: * </pre>
122: */
123: public synchronized String findNextValue(String k, String v) {
124: boolean foundV = false;
125: if (k == null) {
126: for (int i = nkeys; --i >= 0;)
127: if (keys[i] == null)
128: if (foundV)
129: return values[i];
130: else if (values[i] == v)
131: foundV = true;
132: } else
133: for (int i = nkeys; --i >= 0;)
134: if (k.equalsIgnoreCase(keys[i]))
135: if (foundV)
136: return values[i];
137: else if (values[i] == v)
138: foundV = true;
139: return null;
140: }
141:
142: class HeaderIterator implements Iterator {
143: int index = 0;
144: int next = -1;
145: String key;
146: boolean haveNext = false;
147: Object lock;
148:
149: public HeaderIterator(String k, Object lock) {
150: key = k;
151: this .lock = lock;
152: }
153:
154: public boolean hasNext() {
155: synchronized (lock) {
156: if (haveNext) {
157: return true;
158: }
159: while (index < nkeys) {
160: if (key.equalsIgnoreCase(keys[index])) {
161: haveNext = true;
162: next = index++;
163: return true;
164: }
165: index++;
166: }
167: return false;
168: }
169: }
170:
171: public Object next() {
172: synchronized (lock) {
173: if (haveNext) {
174: haveNext = false;
175: return values[next];
176: }
177: if (hasNext()) {
178: return next();
179: } else {
180: throw new NoSuchElementException("No more elements");
181: }
182: }
183: }
184:
185: public void remove() {
186: throw new UnsupportedOperationException(
187: "remove not allowed");
188: }
189: }
190:
191: /**
192: * return an Iterator that returns all values of a particular
193: * key in sequence
194: */
195: public Iterator multiValueIterator(String k) {
196: return new HeaderIterator(k, this );
197: }
198:
199: public synchronized Map getHeaders() {
200: return getHeaders(null);
201: }
202:
203: public synchronized Map getHeaders(String[] excludeList) {
204: boolean skipIt = false;
205: Map m = new HashMap();
206: for (int i = nkeys; --i >= 0;) {
207: if (excludeList != null) {
208: // check if the key is in the excludeList.
209: // if so, don't include it in the Map.
210: for (int j = 0; j < excludeList.length; j++) {
211: if ((excludeList[j] != null)
212: && (excludeList[j]
213: .equalsIgnoreCase(keys[i]))) {
214: skipIt = true;
215: break;
216: }
217: }
218: }
219: if (!skipIt) {
220: List l = (List) m.get(keys[i]);
221: if (l == null) {
222: l = new ArrayList();
223: m.put(keys[i], l);
224: }
225: l.add(values[i]);
226: } else {
227: // reset the flag
228: skipIt = false;
229: }
230: }
231:
232: Set keySet = m.keySet();
233: for (Iterator i = keySet.iterator(); i.hasNext();) {
234: Object key = i.next();
235: List l = (List) m.get(key);
236: m.put(key, Collections.unmodifiableList(l));
237: }
238:
239: return Collections.unmodifiableMap(m);
240: }
241:
242: /** Prints the key-value pairs represented by this
243: header. Also prints the RFC required blank line
244: at the end. Omits pairs with a null key. */
245: public synchronized void print(PrintStream p) {
246: for (int i = 0; i < nkeys; i++)
247: if (keys[i] != null) {
248: p.print(keys[i]
249: + (values[i] != null ? ": " + values[i] : "")
250: + "\r\n");
251: }
252: p.print("\r\n");
253: p.flush();
254: }
255:
256: /** Adds a key value pair to the end of the
257: header. Duplicates are allowed */
258: public synchronized void add(String k, String v) {
259: grow();
260: keys[nkeys] = k;
261: values[nkeys] = v;
262: nkeys++;
263: }
264:
265: /** Prepends a key value pair to the beginning of the
266: header. Duplicates are allowed */
267: public synchronized void prepend(String k, String v) {
268: grow();
269: for (int i = nkeys; i > 0; i--) {
270: keys[i] = keys[i - 1];
271: values[i] = values[i - 1];
272: }
273: keys[0] = k;
274: values[0] = v;
275: nkeys++;
276: }
277:
278: /** Overwrite the previous key/val pair at location 'i'
279: * with the new k/v. If the index didn't exist before
280: * the key/val is simply tacked onto the end.
281: */
282:
283: public synchronized void set(int i, String k, String v) {
284: grow();
285: if (i < 0) {
286: return;
287: } else if (i >= nkeys) {
288: add(k, v);
289: } else {
290: keys[i] = k;
291: values[i] = v;
292: }
293: }
294:
295: /** grow the key/value arrays as needed */
296:
297: private void grow() {
298: if (keys == null || nkeys >= keys.length) {
299: String[] nk = new String[nkeys + 4];
300: String[] nv = new String[nkeys + 4];
301: if (keys != null)
302: System.arraycopy(keys, 0, nk, 0, nkeys);
303: if (values != null)
304: System.arraycopy(values, 0, nv, 0, nkeys);
305: keys = nk;
306: values = nv;
307: }
308: }
309:
310: /**
311: * Remove the key from the header. If there are multiple values under
312: * the same key, they are all removed.
313: * Nothing is done if the key doesn't exist.
314: * After a remove, the other pairs' order are not changed.
315: * @param k the key to remove
316: */
317: public synchronized void remove(String k) {
318: if (k == null) {
319: for (int i = 0; i < nkeys; i++) {
320: while (keys[i] == null && i < nkeys) {
321: for (int j = i; j < nkeys - 1; j++) {
322: keys[j] = keys[j + 1];
323: values[j] = values[j + 1];
324: }
325: nkeys--;
326: }
327: }
328: } else {
329: for (int i = 0; i < nkeys; i++) {
330: while (k.equalsIgnoreCase(keys[i]) && i < nkeys) {
331: for (int j = i; j < nkeys - 1; j++) {
332: keys[j] = keys[j + 1];
333: values[j] = values[j + 1];
334: }
335: nkeys--;
336: }
337: }
338: }
339: }
340:
341: /** Sets the value of a key. If the key already
342: exists in the header, it's value will be
343: changed. Otherwise a new key/value pair will
344: be added to the end of the header. */
345: public synchronized void set(String k, String v) {
346: for (int i = nkeys; --i >= 0;)
347: if (k.equalsIgnoreCase(keys[i])) {
348: values[i] = v;
349: return;
350: }
351: add(k, v);
352: }
353:
354: /** Set's the value of a key only if there is no
355: * key with that value already.
356: */
357:
358: public synchronized void setIfNotSet(String k, String v) {
359: if (findValue(k) == null) {
360: add(k, v);
361: }
362: }
363:
364: /** Convert a message-id string to canonical form (strips off
365: leading and trailing <>s) */
366: public static String canonicalID(String id) {
367: if (id == null)
368: return "";
369: int st = 0;
370: int len = id.length();
371: boolean substr = false;
372: int c;
373: while (st < len && ((c = id.charAt(st)) == '<' || c <= ' ')) {
374: st++;
375: substr = true;
376: }
377: while (st < len
378: && ((c = id.charAt(len - 1)) == '>' || c <= ' ')) {
379: len--;
380: substr = true;
381: }
382: return substr ? id.substring(st, len) : id;
383: }
384:
385: /** Parse a MIME header from an input stream. */
386: public void parseHeader(InputStream is) throws java.io.IOException {
387: synchronized (this ) {
388: nkeys = 0;
389: }
390: mergeHeader(is);
391: }
392:
393: /** Parse and merge a MIME header from an input stream. */
394: public void mergeHeader(InputStream is) throws java.io.IOException {
395: if (is == null)
396: return;
397: char s[] = new char[10];
398: int firstc = is.read();
399: while (firstc != '\n' && firstc != '\r' && firstc >= 0) {
400: int len = 0;
401: int keyend = -1;
402: int c;
403: boolean inKey = firstc > ' ';
404: s[len++] = (char) firstc;
405: parseloop: {
406: while ((c = is.read()) >= 0) {
407: switch (c) {
408: case ':':
409: if (inKey && len > 0)
410: keyend = len;
411: inKey = false;
412: break;
413: case '\t':
414: c = ' ';
415: case ' ':
416: inKey = false;
417: break;
418: case '\r':
419: case '\n':
420: firstc = is.read();
421: if (c == '\r' && firstc == '\n') {
422: firstc = is.read();
423: if (firstc == '\r')
424: firstc = is.read();
425: }
426: if (firstc == '\n' || firstc == '\r'
427: || firstc > ' ')
428: break parseloop;
429: /* continuation */
430: c = ' ';
431: break;
432: }
433: if (len >= s.length) {
434: char ns[] = new char[s.length * 2];
435: System.arraycopy(s, 0, ns, 0, len);
436: s = ns;
437: }
438: s[len++] = (char) c;
439: }
440: firstc = -1;
441: }
442: while (len > 0 && s[len - 1] <= ' ')
443: len--;
444: String k;
445: if (keyend <= 0) {
446: k = null;
447: keyend = 0;
448: } else {
449: k = String.copyValueOf(s, 0, keyend);
450: if (keyend < len && s[keyend] == ':')
451: keyend++;
452: while (keyend < len && s[keyend] <= ' ')
453: keyend++;
454: }
455: String v;
456: if (keyend >= len)
457: v = new String();
458: else
459: v = String.copyValueOf(s, keyend, len - keyend);
460: add(k, v);
461: }
462: }
463:
464: public synchronized String toString() {
465: String result = super .toString() + nkeys + " pairs: ";
466: for (int i = 0; i < keys.length && i < nkeys; i++) {
467: result += "{" + keys[i] + ": " + values[i] + "}";
468: }
469: return result;
470: }
471: }
|