/******************************************************************************* * Copyright (c) 2009 MATERNA Information & Communications. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html. For further * project-related information visit http://www.ws4d.org. The most recent * version of the JMEDS framework can be obtained from * http://sourceforge.net/projects/ws4d-javame. ******************************************************************************/ package org.ws4d.java.util; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.ws4d.java.attachment.AttachmentException; import org.ws4d.java.attachment.IncomingAttachment; import org.ws4d.java.communication.ProtocolException; import org.ws4d.java.constants.MIMEConstants; import org.ws4d.java.constants.Specialchars; import org.ws4d.java.structures.HashMap; import org.ws4d.java.structures.Iterator; /** * Utility class for MIME handling. */ public class MIMEUtil { public static int DEFAULT_MIME_BUFFER = 1024; // predefined exception messages. protected static final String FAULT_UNEXPECTED_END = "Unexpected end of stream."; protected static final String FAULT_MALFORMED_HEADERFIELD = "Malformed MIME header field."; protected static final String FAULT_NOT_FINISHED = "Previous part not finished."; /** * Reads the boundary string. * * @param in input stream to read from. * @param boundary the given boundary information. * @return the read element. * @throws IOException */ public static boolean readBoundary(InputStream in, byte[] boundary) throws IOException { int i = -1; int j = 0; int maxlen = boundary.length; /* * Check for boundary two hyphen characters. See RFC2046 5.1.1 */ i = in.read(); if ((byte) i != MIMEConstants.BOUNDARY_HYPHEN) { return false; } i = in.read(); if ((byte) i != MIMEConstants.BOUNDARY_HYPHEN) { return false; } // Check for boundary while ((j < maxlen) && ((i = in.read()) != -1) && ((byte) i == boundary[j])) { j++; if (j == maxlen) { i = in.read(); if ((byte) i == Specialchars.CR) { i = in.read(); if ((byte) i == Specialchars.LF) { return true; } } } } return false; } /** * Writes a MIME boundary. * * @param out * @param boundary * @param crlf * @param last * @throws IOException */ public static void writeBoundary(OutputStream out, byte[] boundary, boolean crlf, boolean last) throws IOException { if (crlf) { out.write(Specialchars.CR); out.write(Specialchars.LF); } out.write(MIMEConstants.BOUNDARY_HYPHEN); out.write(MIMEConstants.BOUNDARY_HYPHEN); out.write(boundary); if (last) { out.write(MIMEConstants.BOUNDARY_HYPHEN); out.write(MIMEConstants.BOUNDARY_HYPHEN); } out.write(Specialchars.CR); out.write(Specialchars.LF); } public static void serializeAttachment(OutputStream out, IncomingAttachment attachment) throws IOException, AttachmentException { byte[] buffer = new byte[DEFAULT_MIME_BUFFER]; InputStream in = attachment.getInputStream(); int i; int size = 0; long time = System.currentTimeMillis(); while ((i = in.read(buffer)) > 0) { out.write(buffer, 0, i); size += i; } out.flush(); if (Log.isDebug()) { Log.debug("Attachment serialized: " + (System.currentTimeMillis() - time) + "ms. " + size + " bytes.", Log.DEBUG_LAYER_COMMUNICATION); } } /** * Writes MIME header fields to the output stream. * * @param out * @param headerfields * @throws IOException */ public static void writeHeaderFields(OutputStream out, HashMap headerfields) throws IOException { if (headerfields == null) { out.write(Specialchars.CR); out.write(Specialchars.LF); return; } Iterator keys = headerfields.keySet().iterator(); if (keys == null) { out.write(Specialchars.CR); out.write(Specialchars.LF); return; } while (keys.hasNext()) { String fieldname = (String) keys.next(); String fieldvalue = (String) headerfields.get(fieldname); out.write(fieldname.getBytes()); out.write(Specialchars.COL); out.write(Specialchars.SP); out.write(fieldvalue.getBytes()); out.write(Specialchars.CR); out.write(Specialchars.LF); } out.write(Specialchars.CR); out.write(Specialchars.LF); } /** * Reads MIME header fields from the input stream. To learn more about MIME * header fields, take a look at RFC2045 3. * * @param in the input stream to read from. * @param headerfields <code>Hashtable</code> to store the fields in. */ public static void readHeaderFields(InputStream in, HashMap headerfields) throws IOException, ProtocolException { String fieldname = null; String fieldvalue = null; int i; StringBuffer buffer = new StringBuffer(); int j = 0; // length of read bytes. int k = 0; // CRLF counter. 2xCRLF = header end. int l = 0; // CRLF detection. 0=nothing, 1=CR, 2=CRLF. // message-header = field-name ":" [ field-value ] // field-name = token // field-value = *( field-content | LWS ) field-content = *TEXT | // *(token, separators, quoted-string) while (((i = in.read()) != -1)) { if (fieldname == null) { // check for new line if ((byte) i == Specialchars.CR) { l = 1; continue; } // check for new line end if ((byte) i == Specialchars.LF && l == 1) { l = 0; return; } // check for colon and create field-name if ((byte) i == Specialchars.COL) { fieldname = buffer.toString().toLowerCase(); buffer = new StringBuffer(); j = 0; continue; } // no CTL (ascii 0-31) allowed for field-name if ((byte) i >= 0x00 && (char) i <= 0x1F) { // throw new ProtocolException(FAULT_MALFORMED_HEADERFIELD); } // no separators allowed for token (see RFC2616 2.2) if ((byte) i == 0x28 || (byte) i == 0x29 || (byte) i == 0x3C || (byte) i == 0x3D || (byte) i == 0x3E || (byte) i == 0x40 || (byte) i == 0x2C || (byte) i == 0x3F || (byte) i == 0x3B || (byte) i == 0x2F || (byte) i == 0x5C || (byte) i == 0x5B || (byte) i == 0x5D || (byte) i == 0x7B || (byte) i == 0x7D || (byte) i == 0x22 || (byte) i == Specialchars.SP || (byte) i == Specialchars.HT) { throw new ProtocolException(FAULT_MALFORMED_HEADERFIELD); } } else { // if field-name set, must read field-value. if (((byte) i == Specialchars.SP || (byte) i == Specialchars.HT) && j == 0) { buffer.append((char) Specialchars.SP); j++; continue; } // check for new line if ((byte) i == Specialchars.CR) { l = 1; } // check for new line end if ((byte) i == Specialchars.LF && l == 1) { j = 0; k++; l = 2; } if (k > 1) { // add fieldvalue = buffer.toString(); fieldvalue = fieldvalue.trim(); fieldname = fieldname.toLowerCase(); /* * Every MIME body header field must begin with "content-" * like described in RFC2045 3. */ if (fieldname.startsWith(MIMEConstants.DEFAULT_HEADERFIELD_PREFIX.toLowerCase())) { headerfields.put(fieldname, fieldvalue); } // double CRLF, header ends here j = 0; k = 0; l = 0; fieldname = null; fieldvalue = null; return; } if (l > 0) { if (l == 2) { l = 0; } continue; } if (j == 0) { // add filed-name and field-value fieldvalue = buffer.toString(); fieldvalue = fieldvalue.trim(); fieldname = fieldname.toLowerCase(); /* * Every MIME body header field must begin with "content-" * like described in RFC2045 3. */ if (fieldname.startsWith(MIMEConstants.DEFAULT_HEADERFIELD_PREFIX.toLowerCase())) { headerfields.put(fieldname, fieldvalue); } // reset buffer = new StringBuffer(); fieldname = null; fieldvalue = null; } } buffer.append((char) i); j++; k = 0; l = 0; } throw new IOException(FAULT_MALFORMED_HEADERFIELD); } /** * Gets estimated Content-Type via filename. * * @param filename fileName with extension. * @return Content-Type estimated Content-Type based on the file extension. */ public static String estimateContentType(String filename) { int last = 0; last = filename.lastIndexOf('.'); String fileExt = filename.substring(last + 1); return extensionContentType(fileExt); } /** * Returns a file extension that is most likely with given Content-Type * * @param mime MIME Type * @return Extension probable file extension, "" if Content- Type is unknown */ public static String contentToExtension(String mime) { if (MIMEUtil.isValidConstructedMIMEType(mime)) { int sep = mime.indexOf("/"); String mediatype = mime.substring(0, sep); String subtype = mime.substring(sep + 1); if (StringUtil.equalsIgnoreCase(mediatype, MIMEConstants.MEDIATYPE_IMAGE)) { if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_GIF)) { return "*.gif"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_JPEG)) { return "*.jpg"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_PNG)) { return "*.png"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_TIFF)) { return "*.tiff"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_ICON)) { return "*.ico"; } } else if (StringUtil.equalsIgnoreCase(mediatype, MIMEConstants.MEDIATYPE_TEXT)) { if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_CSS)) { return "*.css"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_HTML)) { return "*.htm"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_JAVASCRIPT)) { return "*.js"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_PLAIN)) { return "*.txt"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_RICHTEXT)) { return "*.rtf"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_SOAPXML)) { return "*.xml"; } } else if (StringUtil.equalsIgnoreCase(mediatype, MIMEConstants.MEDIATYPE_APPLICATION)) { if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_MSEXCEL)) { return "*.xls"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_MSWORD)) { return "*.doc"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_RAR)) { return "*.rar"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_PDF)) { return "*.pdf"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_SHOCKWAVEFLASH)) { return "*.swf"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_WINDOWSEXECUTEABLE)) { return "*.exe"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_ZIP)) { return "*.zip"; } } else if (StringUtil.equalsIgnoreCase(mediatype, MIMEConstants.MEDIATYPE_VIDEO)) { if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_WINDOWSMEDIA)) { return "*.wmv"; } else if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_AVI)) { return "*.avi"; } } else if (StringUtil.equalsIgnoreCase(mediatype, MIMEConstants.MEDIATYPE_AUDIO)) { if (StringUtil.equalsIgnoreCase(subtype, MIMEConstants.SUBTYPE_MPEG3)) { return "*.mp3"; } } } return "*.*"; } /** * Gets Content-Type via file extension. * * @param fileExt file extension. * @return Content-Type (type and subtype). */ public static String extensionContentType(String fileExt) { if (StringUtil.equalsIgnoreCase(fileExt, "jpg") || StringUtil.equalsIgnoreCase(fileExt, "jpeg")) { return MIMEConstants.MEDIATYPE_IMAGE + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_JPEG; } else if (StringUtil.equalsIgnoreCase(fileExt, "txt")) { return MIMEConstants.MEDIATYPE_TEXT + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_PLAIN; } else if (StringUtil.equalsIgnoreCase(fileExt, "gif")) { return MIMEConstants.MEDIATYPE_IMAGE + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_GIF; } else if (StringUtil.equalsIgnoreCase(fileExt, "png")) { return MIMEConstants.MEDIATYPE_IMAGE + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_PNG; } else if (StringUtil.equalsIgnoreCase(fileExt, "tiff")) { return MIMEConstants.MEDIATYPE_IMAGE + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_TIFF; } else if (StringUtil.equalsIgnoreCase(fileExt, "tif")) { return MIMEConstants.MEDIATYPE_IMAGE + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_TIFF; } else if (StringUtil.equalsIgnoreCase(fileExt, "htm") || StringUtil.equalsIgnoreCase(fileExt, "html")) { return MIMEConstants.MEDIATYPE_TEXT + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_HTML; } else if (StringUtil.equalsIgnoreCase(fileExt, "xml")) { return MIMEConstants.MEDIATYPE_TEXT + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_XML; } else if (StringUtil.equalsIgnoreCase(fileExt, "js")) { return MIMEConstants.MEDIATYPE_TEXT + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_JAVASCRIPT; } else if (StringUtil.equalsIgnoreCase(fileExt, "css")) { return MIMEConstants.MEDIATYPE_TEXT + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_CSS; } else if (StringUtil.equalsIgnoreCase(fileExt, "zip")) { return MIMEConstants.MEDIATYPE_APPLICATION + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_ZIP; } else if (StringUtil.equalsIgnoreCase(fileExt, "pdf")) { return MIMEConstants.MEDIATYPE_APPLICATION + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_PDF; } else if (StringUtil.equalsIgnoreCase(fileExt, "wmv")) { return MIMEConstants.MEDIATYPE_VIDEO + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_WINDOWSMEDIA; } else if (StringUtil.equalsIgnoreCase(fileExt, "rar")) { return MIMEConstants.MEDIATYPE_APPLICATION + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_RAR; } else if (StringUtil.equalsIgnoreCase(fileExt, "swf")) { return MIMEConstants.MEDIATYPE_APPLICATION + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_SHOCKWAVEFLASH; } else if (StringUtil.equalsIgnoreCase(fileExt, "exe")) { return MIMEConstants.MEDIATYPE_APPLICATION + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_WINDOWSEXECUTEABLE; } else if (StringUtil.equalsIgnoreCase(fileExt, "avi")) { return MIMEConstants.MEDIATYPE_VIDEO + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_AVI; } else if (StringUtil.equalsIgnoreCase(fileExt, "doc") || StringUtil.equalsIgnoreCase(fileExt, "dot")) { return MIMEConstants.MEDIATYPE_APPLICATION + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_MSWORD; } else if (StringUtil.equalsIgnoreCase(fileExt, "ico")) { return MIMEConstants.MEDIATYPE_IMAGE + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_ICON; } else if (StringUtil.equalsIgnoreCase(fileExt, "mp2") || StringUtil.equalsIgnoreCase(fileExt, "mp3")) { return MIMEConstants.MEDIATYPE_AUDIO + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_MPEG3; } else if (StringUtil.equalsIgnoreCase(fileExt, "rtf")) { return MIMEConstants.MEDIATYPE_TEXT + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_RICHTEXT; } else if (StringUtil.equalsIgnoreCase(fileExt, "xls") || StringUtil.equalsIgnoreCase(fileExt, "xla")) { return MIMEConstants.MEDIATYPE_APPLICATION + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_MSEXCEL; } return MIMEConstants.MEDIATYPE_TEXT + MIMEConstants.SEPARATOR + MIMEConstants.SUBTYPE_PLAIN; } /** * Checks if a given string can be a valid MIME- Type * * @param mime String which should be checked * @return boolean returns true if given string can be a correct MIME- Type, * false otherwise */ private static boolean isValidConstructedMIMEType(String mime) { // mime is null if (mime == null) return false; // mime is empty if (mime.length() == 0) return false; int sep = mime.indexOf("/"); // mime does not contain "/" if (sep == -1) return false; // no mediatype if (sep == 0) return false; // no subtype if (mime.length() <= sep + 1) return false; return true; } }