001: /*
002: * MimePart.java
003: *
004: * Copyright (C) 2000-2003 Peter Graves
005: * $Id: MimePart.java,v 1.2 2003/06/29 00:19:34 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j.mail;
023:
024: import java.io.BufferedOutputStream;
025: import java.io.ByteArrayOutputStream;
026: import java.io.FileOutputStream;
027: import java.io.IOException;
028: import java.io.UnsupportedEncodingException;
029: import java.util.ArrayList;
030: import java.util.List;
031: import java.util.Vector;
032: import org.armedbear.j.Directories;
033: import org.armedbear.j.Editor;
034: import org.armedbear.j.File;
035: import org.armedbear.j.FastStringBuffer;
036: import org.armedbear.j.Headers;
037: import org.armedbear.j.Log;
038: import org.armedbear.j.StringPair;
039: import org.armedbear.j.Utilities;
040:
041: public class MimePart {
042: protected String raw;
043: protected Headers headers;
044:
045: private Vector parts;
046:
047: public MimePart(String raw) {
048: this .raw = raw;
049: }
050:
051: public MimePart(String raw, Headers headers) {
052: this .raw = raw;
053: this .headers = headers;
054: }
055:
056: public final String getRawText() {
057: return raw;
058: }
059:
060: public String getAllHeaders() {
061: if (raw.startsWith("\r\n"))
062: return "\r\n";
063: if (raw.startsWith("\n"))
064: return "\n";
065: int index = raw.indexOf("\r\n\r\n");
066: if (index >= 0)
067: return RFC2047.decode(raw.substring(0, index + 2));
068: index = raw.indexOf("\n\n");
069: if (index >= 0)
070: return RFC2047.decode(raw.substring(0, index + 1));
071: return raw;
072: }
073:
074: public String getRawHeaders() {
075: if (raw.startsWith("\r\n"))
076: return "\r\n";
077: if (raw.startsWith("\n"))
078: return "\n";
079: int index = raw.indexOf("\r\n\r\n");
080: if (index >= 0)
081: return raw.substring(0, index + 2);
082: index = raw.indexOf("\n\n");
083: if (index >= 0)
084: return raw.substring(0, index + 1);
085: return raw;
086: }
087:
088: public String getRawBody() {
089: if (raw.startsWith("\r\n"))
090: return raw.substring(2);
091: else if (raw.startsWith("\n"))
092: return raw.substring(1);
093: else {
094: int index = raw.indexOf("\r\n\r\n");
095: if (index >= 0)
096: return raw.substring(index + 4);
097: else {
098: index = raw.indexOf("\n\n");
099: if (index >= 0)
100: return raw.substring(index + 2);
101: else
102: return raw;
103: }
104: }
105: }
106:
107: public String getDecodedBody() {
108: final String rawBody = getRawBody();
109: final String contentType = getContentType();
110: final String transferEncoding = getTransferEncoding();
111: final String charset = Utilities
112: .getCharsetFromContentType(getHeaderValue(Headers.CONTENT_TYPE));
113: final String characterEncoding = Utilities
114: .getEncodingFromCharset(charset);
115: if (contentType == null
116: || contentType.toLowerCase().startsWith("text/")) {
117: if (transferEncoding == null
118: || transferEncoding.equals("7bit")
119: || transferEncoding.equals("8bit")
120: || transferEncoding.equals("binary"))
121: return rawBody;
122: else if (transferEncoding.equals("quoted-printable")) {
123: byte[] bytes = QuotedPrintableDecoder.decode(rawBody);
124: try {
125: return new String(bytes, characterEncoding);
126: } catch (UnsupportedEncodingException e) {
127: Log.error(e);
128: return new String(bytes);
129: }
130: } else if (transferEncoding.equals("base64")) {
131: try {
132: ByteArrayOutputStream out = new ByteArrayOutputStream();
133: Base64Decoder.decode(rawBody, out); // Ignore errors.
134: byte[] bytes = out.toByteArray();
135: if (bytes != null) {
136: try {
137: return new String(bytes, 0, bytes.length,
138: characterEncoding);
139: } catch (UnsupportedEncodingException e) {
140: Log.error(e);
141: return new String(bytes, 0, bytes.length);
142: }
143: }
144: } catch (IOException e) {
145: Log.error(e);
146: }
147: return null;
148: } else
149: return null;
150: } else if (contentType.startsWith("multipart/"))
151: return rawBody;
152: else if (transferEncoding == null)
153: return rawBody;
154: else
155: return null;
156: }
157:
158: private byte[] getDecodedBodyAsByteArray() {
159: final String rawBody = getRawBody();
160: final String encoding = getTransferEncoding();
161: if (encoding == null || encoding.equals("7bit")
162: || encoding.equals("8bit") || encoding.equals("binary")) {
163: byte[] bytes = null;
164: try {
165: bytes = rawBody.getBytes("ISO8859_1");
166: } catch (UnsupportedEncodingException e) {
167: Log.error(e);
168: }
169: return bytes;
170: }
171: if (encoding.equals("quoted-printable"))
172: return QuotedPrintableDecoder.decode(rawBody);
173: if (encoding.equals("base64")) {
174: try {
175: ByteArrayOutputStream out = new ByteArrayOutputStream();
176: if (Base64Decoder.decode(rawBody, out))
177: return out.toByteArray();
178: } catch (IOException e) {
179: Log.error(e);
180: }
181: // Fall through, return null...
182: }
183: return null;
184: }
185:
186: public Vector getParts() {
187: return parts;
188: }
189:
190: public MimePart getPart(int i) {
191: if (parts == null)
192: return null;
193: if (i < 0)
194: return null;
195: if (i >= parts.size())
196: return null;
197: return (MimePart) parts.get(i);
198: }
199:
200: protected void addParts(Vector v) {
201: v.add(this );
202: // Recurse.
203: if (parts != null) {
204: for (int i = 0; i < parts.size(); i++) {
205: MimePart part = (MimePart) parts.get(i);
206: String contentType = part.getContentType();
207: if (contentType != null
208: && contentType.startsWith("multipart/"))
209: part.addParts(v);
210: else
211: v.add(part);
212: }
213: }
214: }
215:
216: public final Headers getHeaders() {
217: if (headers == null)
218: headers = Headers.parse(raw);
219: return headers;
220: }
221:
222: public final String getHeaderValue(int index) {
223: if (headers == null)
224: headers = Headers.parse(raw);
225: return headers.getValue(index);
226: }
227:
228: public final String getContentType() {
229: String s = getHeaderValue(Headers.CONTENT_TYPE);
230: if (s == null)
231: return null;
232: s = s.trim();
233: if (s.length() == 0)
234: return null;
235: int index = s.indexOf(';');
236: if (index >= 0)
237: s = s.substring(0, index);
238: return s.toLowerCase();
239: }
240:
241: public final String getTransferEncoding() {
242: String s = getHeaderValue(Headers.CONTENT_TRANSFER_ENCODING);
243: if (s == null)
244: return null;
245: return s.toLowerCase();
246: }
247:
248: public final String getDisposition() {
249: String s = getHeaderValue(Headers.CONTENT_DISPOSITION);
250: if (s == null)
251: return null;
252: s = s.trim();
253: int index = s.indexOf(';');
254: if (index >= 0)
255: s = s.substring(0, index);
256: return s;
257: }
258:
259: public final int getSize() {
260: String s = getRawBody();
261: return s == null ? 0 : s.length();
262: }
263:
264: public final boolean isAttachment() {
265: String s = getHeaderValue(Headers.CONTENT_DISPOSITION);
266: if (s != null && s.trim().startsWith("attachment"))
267: return true;
268: else
269: return false;
270: }
271:
272: public final boolean isInline() {
273: String s = getHeaderValue(Headers.CONTENT_DISPOSITION);
274: if (s != null && s.trim().startsWith("inline"))
275: return true;
276: return false;
277: }
278:
279: public String getAttachmentFileName() {
280: String filename = getHeaderParameter(
281: Headers.CONTENT_DISPOSITION, "filename");
282: if (filename == null)
283: filename = getHeaderParameter(Headers.CONTENT_TYPE, "name");
284: if (filename != null) {
285: filename = filename.trim();
286: int length = filename.length();
287: // Strip quotes.
288: if (length >= 2 && filename.charAt(0) == '"'
289: && filename.charAt(length - 1) == '"')
290: filename = filename.substring(1, length - 1);
291: // Filename might be RFC2047-encoded.
292: filename = RFC2047.decode(filename);
293: // Remove path prefix, if any.
294: int index = filename.lastIndexOf('/');
295: if (index < 0)
296: index = filename.lastIndexOf('\\');
297: if (index >= 0)
298: filename = filename.substring(index + 1);
299: }
300: return filename;
301: }
302:
303: private String getHeaderParameter(int header, String parameterName) {
304: String s = getHeaderValue(header);
305: if (s != null) {
306: s = s.trim();
307: String lower = s.toLowerCase();
308: int index = lower.indexOf(parameterName.concat("="));
309: if (index >= 0) {
310: int begin = index + parameterName.length() + 1;
311: int end = s.indexOf(';', begin);
312: if (end >= 0)
313: return s.substring(begin, end);
314: else
315: return s.substring(begin);
316: }
317: }
318: return null;
319: }
320:
321: public File cacheDecoded() {
322: String filename = getAttachmentFileName();
323: String extension = null;
324: if (filename != null && filename.length() > 0) {
325: extension = Utilities.getExtension(filename);
326: } else {
327: // No filename specified.
328: String contentType = getContentType();
329: if (contentType != null) {
330: if (contentType.equals("image/jpeg"))
331: extension = ".jpg";
332: else if (contentType.equals("image/gif"))
333: extension = ".gif";
334: else if (contentType.equals("text/html"))
335: extension = ".html";
336: }
337: }
338: File cache = Utilities.getTempFile(Directories
339: .getTempDirectory(), extension);
340: if (cache != null && saveDecoded(cache))
341: return cache;
342: else
343: return null;
344: }
345:
346: public boolean saveDecoded(File file) {
347: String encoding = getTransferEncoding();
348: if (encoding == null || encoding.equals("7bit")
349: || encoding.equals("8bit") || encoding.equals("binary")
350: || encoding.equals("quoted-printable")) {
351: try {
352: byte[] bytes = getDecodedBodyAsByteArray();
353: if (bytes == null)
354: return false;
355: FileOutputStream out = file.getOutputStream();
356: out.write(bytes);
357: out.flush();
358: out.close();
359: return true;
360: } catch (IOException e) {
361: Log.error(e);
362: return false;
363: }
364: } else if (encoding.equals("base64")) {
365: return saveDecodedBase64(file);
366: } else if (encoding.equals("x-uuencode")) {
367: Log.error("saveDecoded x-uuencode not supported");
368: return false;
369: } else {
370: Log.error("saveDecoded unrecognized encoding " + encoding);
371: return false;
372: }
373: }
374:
375: private boolean saveDecodedBase64(File file) {
376: boolean success = false;
377: try {
378: BufferedOutputStream out = new BufferedOutputStream(file
379: .getOutputStream());
380: String rawBody = getRawBody();
381: success = Base64Decoder.decode(rawBody, out);
382: out.flush();
383: out.close();
384: } catch (IOException e) {
385: Log.error(e);
386: success = false;
387: }
388: if (!success)
389: file.delete();
390: return success;
391: }
392:
393: private static final String BOUNDARY_START = "boundary=";
394:
395: public void parse() {
396: final String contentType = getHeaderValue(Headers.CONTENT_TYPE);
397: if (contentType == null)
398: return;
399: if (contentType.toLowerCase().startsWith("multipart/")) {
400: int index = contentType.toLowerCase().indexOf(
401: BOUNDARY_START);
402: if (index < 0) {
403: Log.error("can't find boundary parameter");
404: return;
405: }
406: String boundary = contentType.substring(
407: index + BOUNDARY_START.length()).trim();
408: if (boundary.length() >= 2 && boundary.charAt(0) == '"') {
409: int end = boundary.indexOf('"', 1);
410: if (end >= 0)
411: boundary = boundary.substring(1, end);
412: }
413: parts = parseParts(boundary);
414: if (parts != null) {
415: for (int i = 0; i < parts.size(); i++) {
416: MimePart part = (MimePart) parts.get(i);
417: part.parse();
418: }
419: }
420: return;
421: }
422: final String disposition = getDisposition();
423: if (disposition != null
424: && disposition.equalsIgnoreCase("attachment")) {
425: parts = new Vector();
426: MimePart part = new MimePart(raw);
427: parts.add(part);
428: }
429: }
430:
431: private Vector parseParts(String boundary) {
432: final String marker = "--" + boundary;
433: final String endMarker = marker + "--";
434: final int veryEnd = raw.indexOf(endMarker);
435: int start = raw.indexOf(marker);
436: if (start < 0)
437: return null;
438: Vector v = new Vector();
439: while (true) {
440: start += marker.length();
441: if (raw.charAt(start) == '\r')
442: ++start;
443: if (raw.charAt(start) == '\n')
444: ++start;
445: int end = raw.indexOf(marker, start);
446: if (end < 0)
447: end = raw.length();
448: MimePart part = new MimePart(raw.substring(start, end));
449: v.add(part);
450: if (end == veryEnd || end == raw.length())
451: break;
452: start = end;
453: }
454: return v;
455: }
456: }
|