tdunnick.jphineas.mime.MimeContent.java Source code

Java tutorial

Introduction

Here is the source code for tdunnick.jphineas.mime.MimeContent.java

Source

/*
 *  Copyright (c) 2015-2016 Thomas Dunnick (https://mywebspace.wisc.edu/tdunnick/web)
 *  
 *  This file is part of jPhineas
 *
 *  jPhineas is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  jPhineas is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with jPhineas.  If not, see <http://www.gnu.org/licenses/>.
 */

package tdunnick.jphineas.mime;

import java.util.*;
import org.bouncycastle.util.encoders.*;

/**
 * Mime content management.
 * 
 * @author Thomas Dunnick
 *
 */
public class MimeContent {
    private StringBuffer buf = new StringBuffer();

    /**  MIME content type header tag */
    public static final String CONTENT = "Content-Type";
    /**  MIME text value for CONTENT */
    public static final String TEXT = "text/plain";
    /**  MIME xml value for CONTENT */
    public static final String XML = "text/xml";
    /** MIME octet stream for CONTENT */
    public static final String OCTET = "Application/Octet-Stream";
    /**  MIME multipart value for CONTENT */
    public static final String MULTIPART = "multipart/related";

    /**  MIME encoding header tag */
    public static final String ENCODING = "Content-Transfer-Encoding";
    /**  MIME base64 value for ENCODING */
    public static final String BASE64 = "base64";
    /** MIME quoted printable value for ENCODING */
    public static final String QUOTED = "quoted/printable";

    /** MIME content length header tag */
    public static final String LENGTH = "Content-Length";
    /** MIME Content ID header tag */
    public static final String CONTENTID = "Content-ID";
    /** MIME Message ID Header tag */
    public static final String MESSAGEID = "Message-ID";
    /** MIME disposition tag */
    public static final String DISPOSITION = "Content-Disposition";
    /** current MIME headers */
    private ArrayList<String> headers = new ArrayList<String>();

    /**
     * create an empty MIME content with TEXT CONTENT and empty the body 
     */
    public MimeContent() {
        reset();
    }

    /**
     * create MIME content as parsed from a message
     * @param m message to parse
     */
    public MimeContent(String m) {
        parse(m);
    }

    /**
     * reset the MIME content with TEXT CONTENT and empty the body 
     */
    public void reset() {
        headers.clear();
        setHeader("MIME-Version", "1.0");
        buf.setLength(0);
    }

    /**
     * If the header starts with HTTP, then this is the response from
     * a request and parse out the code.
     * @return HTTP response code or -1 if not a response
     */
    public int getHTTPCode() {
        if (headers.size() < 1)
            return -1;
        String s = headers.get(0);
        if (s.startsWith("HTTP")) {
            s = s.replaceFirst("^.* ([0-9]+) .*$", "$1");
            return Integer.parseInt(s);
        }
        return -1;
    }

    /**
     * Set the MIME CONTENT type
     * @param id of CONTENT
     */
    public void setContentId(String id) {
        setHeader(CONTENTID, id);
    }

    /**
     * Gets the current CONTENT type
     * @return type of content
     */
    public String getContentId() {
        return getHeader(CONTENTID);
    }

    /**
     * Set the MIME CONTENT type
     * @param type of CONTENT
     */
    public void setContentType(String type) {
        if (type.startsWith(MULTIPART))
            setMultiPart();
        else
            setHeader(CONTENT, type);
    }

    /**
     * Gets the current CONTENT type
     * @return type of content
     */
    public String getContentType() {
        return getHeader(CONTENT);
    }

    /**
     * Sets the MIME encoding.  Default is none (no encoding).
     * @param enc encode to use
     */
    public void setEncoding(String enc) {
        setHeader(ENCODING, enc);
    }

    /**
     * Gets the current MIME encoding
     * @return encoding or null if none is set
     */
    public String getEncoding() {
        return getHeader(ENCODING);
    }

    /**
     * Sets the MIME dispostion.
     * @param d disposition to use
     */
    public void setDisposition(String d) {
        setHeader(DISPOSITION, d);
    }

    /**
     * Gets the current MIME disposition
     * @return encoding or null if none is set
     */
    public String getDisposition() {
        return getHeader(DISPOSITION);
    }

    /**
     * Set BASIC authentication
     * TODO set digest authentication
     * 
     * @param uid - user id
     * @param password - user password
     */
    public void setBasicAuth(String uid, String password) {
        if ((uid == null) || (password == null))
            return;
        byte[] encoded = Base64.encode((uid + ":" + password).getBytes());
        setHeader("Authorization", "Basic " + new String(encoded));
    }

    /**
     * Set an arbitrary MIME header. If the value is null,
     * remove the header. Surround ID's with angle brackets as required.
     * 
     * @param name of MIME tag
     * @param value to set
     */
    public void setHeader(String name, String value) {
        setHeader(name, value, -1);
    }

    /**
     * Set the header at a specific position.  If the value is null,
     * remove the header.  If the position is negative append the header.
     * Surround ID's with angle brackets as required.
     * 
     * @param name of header item
     * @param value of header item
     * @param position in header list
     */
    public void setHeader(String name, String value, int position) {
        if (name == null)
            return;
        // ID's must be surrounded with angle brackets
        if (name.equalsIgnoreCase(CONTENTID) || name.equalsIgnoreCase(MESSAGEID)) {
            if ((value != null) && !value.matches("<.*>"))
                value = "<" + value + ">";
        }
        int i = getHeaderIndex(name);
        if (value == null) {
            if (i >= 0)
                headers.remove(i);
            return;
        }
        String v = name + ": " + value;
        if ((position < 0) || (position == i)) {
            if (i < 0)
                headers.add(v);
            else
                headers.set(i, v);
        } else {
            if (i >= 0)
                headers.remove(i);
            if (position < headers.size())
                headers.add(position, v);
            else
                headers.add(v);
        }
    }

    /**
     * Get an arbitrary MIME header value, removing angle brackets from ID's
     * 
     * @param name of MIME tag
     * @return value of tag
     */
    public String getHeader(String name) {
        int i = getHeaderIndex(name);
        if (i < 0)
            return "";
        String s = headers.get(i).replaceFirst("^.*: *", "");
        // remove angle brackets for content or message id's
        if (name.equalsIgnoreCase(CONTENTID) || name.equalsIgnoreCase(MESSAGEID))
            s = s.substring(1, s.length() - 1);
        return s;
    }

    /**
     * Gets the headers index for a tag 
     * @param name of tag
     * @return index or -1 if not found
     */
    private int getHeaderIndex(String name) {
        name = name.toLowerCase();
        for (int i = 0; i < headers.size(); i++) {
            if (headers.get(i).toLowerCase().startsWith(name))
                return i;
        }
        return -1;
    }

    /**
     * Get a list of MIME headers including tags and values
     * @return the list of headers
     */
    public String[] getHeaders() {
        String[] h = new String[headers.size()];
        return headers.toArray(h);
    }

    /**
     * Set the body of the MIME message
     * @param body to set it to
     * @return true if successful
     */
    public boolean setBody(String body) {
        int p = -1;
        String boundary = getBoundary();
        if (boundary != null)
            p = buf.indexOf("\n--" + boundary);
        if (p > 0)
            buf.delete(0, p);
        else if (p < 0)
            buf.setLength(0);
        buf.insert(0, body);
        return true;
    }

    /**
     * Gets the body of the MIME message
     * @return the message body
     */
    public String getBody() {
        int p = -1;
        String boundary = getBoundary();
        if (boundary != null)
            p = buf.indexOf("\n--" + boundary);
        if (p > 0)
            return buf.substring(0, p);
        return buf.toString();
    }

    /**
     * Set encoding to base64 and encode the binary body
     * @param body to encode
     * @return true if successful
     */
    public boolean encodeBody(byte[] body) {
        setEncoding(BASE64);
        setContentType(MimeContent.OCTET);
        setBody(new String(Base64.encode(body)));
        return false;
    }

    /**
     * Gets a binary body, decoding if necessary
     * @return the decoded body
     */
    public byte[] decodeBody() {
        if (getEncoding().equalsIgnoreCase(BASE64))
            return Base64.decode(buf.toString().getBytes());
        return buf.toString().getBytes();
    }

    /**
     * parse the body of this content as POST parameters 
     * @return table of paramters
     */
    public HashMap<String, String> getParams() {
        HashMap<String, String> arg = new HashMap<String, String>();
        String[] t = getBody().split("[&]");
        for (int i = 0; i < t.length; i++) {
            String[] a = t[i].split("=");
            if (a.length == 2)
                arg.put(a[0].trim(), a[1].trim());
        }
        return arg;
    }

    /**
     * Set the body with a map of POST parameters
     * @param params
     */
    public void setParams(HashMap<String, String> params) {
        StringBuffer buf = new StringBuffer();
        Iterator<String> it = params.keySet().iterator();
        while (it.hasNext()) {
            String k = it.next();
            buf.append(k + "=" + params.get(k) + "&");
        }
        setBody(buf.substring(0, buf.length() - 1));
    }

    public boolean setMultiPart() {
        return setMultiPart(null);
    }

    /**
     * Set the MIME content to be multipart and create a boundary
     * based on the time and a hashcode
     * @return true if successful
     */
    public boolean setMultiPart(String args) {
        if (buf.length() > 0)
            return false;
        Date d = new Date();
        String boundary = "_Part_" + d.getTime() + "_" + d.hashCode();
        if (args != null)
            setHeader(CONTENT, MULTIPART + "; boundary=\"" + boundary + "\"; " + args);
        else
            setHeader(CONTENT, MULTIPART + "; boundary=\"" + boundary + "\"");
        return true;
    }

    /**
     * Gets the text used to separate multipart MIME messages.
     * @return boundary or null if not a multipart message
     */
    public String getBoundary() {
        String b = getHeader(CONTENT);
        int i = b.indexOf("boundary=");
        if (i < 0)
            return null;
        i += 9;
        int e = -1;
        if (b.charAt(i) == '"') {
            i++;
            e = b.indexOf('"', i);
        } else
            e = b.indexOf(";", i);
        if (e < 0)
            return b.substring(i);
        return b.substring(i, e);
    }

    /**
     * Get a list of MIME content for a multipart message
     * @return the list or null if not multipart
     */
    public MimeContent[] getMultiParts() {
        String boundary = getBoundary();
        if (boundary == null)
            return null;
        int s = buf.indexOf("--" + boundary + "\n");
        if (s < 0)
            return null;
        else
            s += 3 + boundary.length();
        int e = buf.indexOf("\n--" + boundary + "--", s);
        if (e < 0)
            return null;
        String[] parts = buf.substring(s, e).split("(?s)\n--" + boundary + "\n");
        MimeContent[] mparts = new MimeContent[parts.length];
        for (int i = 0; i < parts.length; i++)
            mparts[i] = new MimeContent(parts[i]);
        return mparts;
    }

    /**
     * Add MIME content to a multipart message
     * @param m
     * @return true if successful
     */
    public boolean addMultiPart(MimeContent m) {
        if (m == null)
            return false;
        String boundary = getBoundary();
        if (boundary == null)
            return false;
        boundary = "\n--" + boundary + "--\n";
        int l = buf.indexOf(boundary);
        if (l < 0) {
            buf.append(boundary.substring(0, boundary.length() - 3) + "\n");
        } else {
            l += boundary.length() - 3;
            buf.delete(l, l + 2);
        }
        buf.append(m.toString() + boundary);
        return true;
    }

    /**
     * Get actual current content length (use getHeader(LENGTH) for "logical")
     * 
     * @return actual content length
     */
    public int getContentLength() {
        return buf.length();
    }

    /**
     * Get the MIME message
     * @return the formatted message
     */
    public String toString() {
        if (getHeader(CONTENT).length() == 0)
            setHeader(CONTENT, TEXT, 1);
        setHeader(LENGTH, "" + buf.length());
        StringBuffer hd = new StringBuffer();
        for (int i = 0; i < headers.size(); i++)
            hd.append(headers.get(i) + "\n");
        return hd.toString() + "\n" + buf.toString();
    }

    /**
     * Parse and store a formated MIME message
     * @param msg to parse
     */
    public boolean parse(String msg) {
        reset();
        int hdrlen = msg.indexOf("\n\n");
        if (hdrlen < CONTENT.length())
            return false;
        String[] header = msg.substring(0, hdrlen).split("(?s)\n");
        if (header.length < 1)
            return false;
        for (int i = 0; i < header.length; i++) {
            headers.add(header[i]);
        }
        buf.append(msg.substring(hdrlen + 2));
        return true;
    }
}