001: /*
002: * Copyright (c) 1998-2001 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.web.webmail;
030:
031: import com.caucho.util.ByteBuffer;
032: import com.caucho.util.CharBuffer;
033: import com.caucho.vfs.ReadStream;
034: import com.caucho.vfs.StreamImpl;
035:
036: import java.io.IOException;
037: import java.util.HashMap;
038: import java.util.Iterator;
039:
040: /*
041: * A stream for reading mbox files.
042: *
043: * <p/>Each call to openRead() will return the next part and return null
044: * when complete.
045: *
046: * <pre><code>
047: * MboxStream mbox = new MboxStream(rawIs);
048: * ReadStream is;
049: *
050: * while ((is = multi.openRead()) != null) {
051: * // read from is as a normal stream
052: * }
053: */
054: public class MboxStream extends StreamImpl {
055: private byte[] boundaryBuffer = "From ".getBytes();
056: private int boundaryLength = 5;
057:
058: private ByteBuffer peekBuffer = new ByteBuffer();
059: private byte[] peek;
060: private int peekOffset;
061: private int peekLength;
062:
063: private byte[] dummyBuffer = new byte[32];
064:
065: private ReadStream is;
066: private ReadStream readStream;
067: private boolean isPartDone;
068: private boolean isDone;
069: private HashMap headers = new HashMap();
070: private CharBuffer line = new CharBuffer();
071:
072: private String defaultEncoding;
073:
074: public MboxStream() throws IOException {
075: }
076:
077: public MboxStream(ReadStream is) throws IOException {
078: this ();
079:
080: init(is);
081: }
082:
083: /**
084: * Returns the default encoding.
085: */
086: public String getEncoding() {
087: return defaultEncoding;
088: }
089:
090: /**
091: * Sets the default encoding.
092: */
093: public void setEncoding(String encoding) {
094: this .defaultEncoding = encoding;
095: }
096:
097: /**
098: * Initialize the multipart stream with a given boundary. The boundary
099: * passed to <code>init</code> will have "--" prefixed.
100: *
101: * @param is the underlying stream
102: * @param headerBoundary the multipart/mime boundary.
103: */
104: public void init(ReadStream is) throws IOException {
105: this .is = is;
106:
107: peekBuffer.setLength(boundaryLength + 5);
108: peek = peekBuffer.getBuffer();
109: peekOffset = 0;
110: peekLength = 0;
111: peek[peekLength++] = (byte) '\n';
112:
113: isPartDone = false;
114: isDone = false;
115:
116: while (read(dummyBuffer, 0, dummyBuffer.length) >= 0) {
117: }
118:
119: isPartDone = true;
120: }
121:
122: /**
123: * Opens the next message of the mbox stream for reading. Returns
124: * null when the last message is read.
125: */
126: public ReadStream openRead() throws IOException {
127: if (isDone)
128: return null;
129: else if (readStream == null)
130: readStream = new ReadStream(this , null);
131: else if (!isPartDone) {
132: int len;
133: while ((len = read(dummyBuffer, 0, dummyBuffer.length)) >= 0) {
134: }
135:
136: if (isDone)
137: return null;
138: }
139:
140: readStream.init(this , null);
141:
142: isPartDone = false;
143:
144: if (scanHeaders()) {
145: String contentType = (String) getAttribute("content-type");
146:
147: String charset = getAttributePart(contentType, "charset");
148:
149: if (charset != null)
150: readStream.setEncoding(charset);
151: else if (defaultEncoding != null)
152: readStream.setEncoding(defaultEncoding);
153:
154: return readStream;
155: } else {
156: isDone = true;
157: readStream.close();
158: return null;
159: }
160: }
161:
162: /**
163: * Returns a read attribute from the multipart mime.
164: */
165: public Object getAttribute(String key) {
166: return headers.get(key.toLowerCase());
167: }
168:
169: /**
170: * Returns the headers from the mime.
171: */
172: public Iterator getAttributeNames() {
173: return headers.keySet().iterator();
174: }
175:
176: /**
177: * Scans the mime headers. The mime headers are in standard mail/http
178: * header format: "key: value".
179: */
180: private boolean scanHeaders() throws IOException {
181: int ch = read();
182:
183: headers.clear();
184: while (ch > 0 && ch != '\n' && ch != '\r') {
185: line.clear();
186:
187: line.append((char) ch);
188: for (ch = read(); ch >= 0 && ch != '\n' && ch != '\r'; ch = read()) {
189: line.append((char) ch);
190: }
191:
192: if (ch == '\r') {
193: if ((ch = read()) == '\n')
194: ch = read();
195: } else if (ch == '\n')
196: ch = read();
197:
198: int i = 0;
199: for (; i < line.length() && line.charAt(i) != ':'; i++) {
200: }
201:
202: String key = null;
203: String value = null;
204: if (i < line.length()) {
205: key = line.substring(0, i).trim().toLowerCase();
206: value = line.substring(i + 1).trim();
207:
208: headers.put(key, value);
209: }
210: }
211:
212: if (ch == '\r') {
213: if ((ch = read()) != '\n') {
214: peek[0] = (byte) ch;
215: peekOffset = 0;
216: peekLength = 1;
217: }
218: }
219:
220: return true;
221: }
222:
223: public boolean canRead() {
224: return true;
225: }
226:
227: /**
228: * Reads from the multipart mime buffer.
229: */
230: public int read(byte[] buffer, int offset, int length)
231: throws IOException {
232: int b = -1;
233:
234: if (isPartDone)
235: return -1;
236:
237: int i = 0;
238: while (i < length && (b = read()) >= 0) {
239: boolean hasCr = false;
240:
241: if (b == '\r') {
242: hasCr = true;
243: b = read();
244:
245: // XXX: Macintosh?
246: if (b != '\n') {
247: buffer[offset + i++] = (byte) '\r';
248: peek[0] = (byte) b;
249: peekOffset = 0;
250: peekLength = 1;
251: continue;
252: }
253: } else if (b != '\n') {
254: buffer[offset + i++] = (byte) b;
255: continue;
256: }
257:
258: int j;
259: for (j = 0; j < boundaryLength && (b = read()) >= 0
260: && boundaryBuffer[j] == b; j++) {
261: }
262:
263: if (j == boundaryLength) {
264: isPartDone = true;
265:
266: while ((b = read()) >= 0 && b != '\r' && b != '\n') {
267: }
268:
269: return 1;
270: }
271:
272: peekLength = 0;
273: if (hasCr && i + 1 < length) {
274: buffer[offset + i++] = (byte) '\r';
275: buffer[offset + i++] = (byte) '\n';
276: } else if (hasCr) {
277: buffer[offset + i++] = (byte) '\r';
278: peek[peekLength++] = (byte) '\n';
279: } else {
280: buffer[offset + i++] = (byte) '\n';
281: }
282:
283: int k = 0;
284: while (k < j && i + 1 < length)
285: buffer[offset + i++] = boundaryBuffer[k++];
286:
287: while (k < j)
288: peek[peekLength++] = boundaryBuffer[k++];
289:
290: peek[peekLength++] = (byte) b;
291: peekOffset = 0;
292: }
293:
294: if (i <= 0) {
295: isPartDone = true;
296: if (b < 0)
297: isDone = true;
298: return -1;
299: } else {
300: return i;
301: }
302: }
303:
304: /**
305: * Read the next byte from the peek or from the underlying stream.
306: */
307: private int read() throws IOException {
308: if (peekOffset < peekLength)
309: return peek[peekOffset++] & 0xff;
310: else
311: return is.read();
312: }
313:
314: private static String getAttributePart(String attr, String name) {
315: if (attr == null)
316: return null;
317:
318: int length = attr.length();
319: int i = attr.indexOf(name);
320: if (i < 0)
321: return null;
322:
323: for (i += name.length(); i < length && attr.charAt(i) != '='; i++) {
324: }
325:
326: for (i++; i < length && attr.charAt(i) == ' '; i++) {
327: }
328:
329: CharBuffer value = CharBuffer.allocate();
330: if (i < length && attr.charAt(i) == '\'') {
331: for (i++; i < length && attr.charAt(i) != '\''; i++)
332: value.append(attr.charAt(i));
333: } else if (i < length && attr.charAt(i) == '"') {
334: for (i++; i < length && attr.charAt(i) != '"'; i++)
335: value.append(attr.charAt(i));
336: } else if (i < length) {
337: char ch;
338: for (; i < length && (ch = attr.charAt(i)) != ' '
339: && ch != ';'; i++)
340: value.append(ch);
341: }
342:
343: return value.close();
344: }
345: }
|