package org.bouncycastle.mime;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.bouncycastle.util.Iterable;
import org.bouncycastle.util.Strings;

public class Headers
    implements Iterable<String>
{
    private final Map<String, List> headers = new TreeMap<String, List>(String.CASE_INSENSITIVE_ORDER);
    private final List<String> headersAsPresented;
    private final String contentTransferEncoding;

    private String boundary;
    private boolean multipart;
    private String contentType;
    private Map<String, String> contentTypeParameters;

    private static List<String> parseHeaders(InputStream src)
        throws IOException
    {
        String s;
        List<String> headerLines = new ArrayList<String>();
        LineReader   rd = new LineReader(src);

        while ((s = rd.readLine()) != null)
        {
            if (s.length() == 0)
            {
                break;
            }
            headerLines.add(s);
        }

        return headerLines;
    }

    public Headers(InputStream source, String defaultContentTransferEncoding)
        throws IOException
    {
        this(parseHeaders(source), defaultContentTransferEncoding);
    }

    public Headers(List<String> headerLines, String defaultContentTransferEncoding)
    {
        this.headersAsPresented = headerLines;

        String header = "";
        for (Iterator it = headerLines.iterator(); it.hasNext();)
        {
            String line = (String)it.next();
            if (line.startsWith(" ") || line.startsWith("\t"))
            {
                header = header + line.trim();
            }
            else
            {
                if (header.length() != 0)
                {
                    this.put(header.substring(0, header.indexOf(':')).trim(), header.substring(header.indexOf(':') + 1).trim());
                }
                header = line;
            }
        }

        // pick up last header line
        if (header.trim().length() != 0)
        {
            this.put(header.substring(0, header.indexOf(':')).trim(), header.substring(header.indexOf(':') + 1).trim());
        }

        String contentTypeHeader = (this.getValues("Content-Type") == null) ? "text/plain" : this.getValues("Content-Type")[0];

        int parameterIndex = contentTypeHeader.indexOf(';');
        if (parameterIndex < 0)
        {
            contentType = contentTypeHeader;
            contentTypeParameters = Collections.EMPTY_MAP;
        }
        else
        {
            contentType = contentTypeHeader.substring(0, parameterIndex);
            contentTypeParameters = createContentTypeParameters(contentTypeHeader.substring(parameterIndex + 1).trim());
        }

        contentTransferEncoding = this.getValues("Content-Transfer-Encoding") == null ? defaultContentTransferEncoding : this.getValues("Content-Transfer-Encoding")[0];

        if (contentType.indexOf("multipart") >= 0)
        {
            multipart = true;
            String bound = (String)contentTypeParameters.get("boundary");
            boundary = bound.substring(1, bound.length() - 1); // quoted-string
        }
        else
        {
            boundary = null;
            multipart = false;
        }
    }

    
Return the a Map of the ContentType attributes and their values.
Returns:a Map of ContentType parameters - empty if none present.
/** * Return the a Map of the ContentType attributes and their values. * * @return a Map of ContentType parameters - empty if none present. */
public Map<String, String> getContentTypeAttributes() { return contentTypeParameters; }
Return the a list of the ContentType parameters.
Returns:a list of ContentType parameters - empty if none present.
/** * Return the a list of the ContentType parameters. * * @return a list of ContentType parameters - empty if none present. */
private Map<String, String> createContentTypeParameters(String contentTypeParameters) { String[] parameterSplit = contentTypeParameters.split(";"); Map<String, String> rv = new LinkedHashMap<String, String>(); for (int i = 0; i != parameterSplit.length; i++) { String parameter = parameterSplit[i]; int eqIndex = parameter.indexOf('='); if (eqIndex < 0) { throw new IllegalArgumentException("malformed Content-Type header"); } rv.put(parameter.substring(0, eqIndex).trim(), parameter.substring(eqIndex + 1).trim()); } return Collections.unmodifiableMap(rv); } public boolean isMultipart() { return multipart; } public String getBoundary() { return boundary; } public String getContentType() { return contentType; } public String getContentTransferEncoding() { return contentTransferEncoding; } private void put(String field, String value) { synchronized (this) { KV kv = new KV(field, value); List<KV> list = (List<KV>)headers.get(field); if (list == null) { list = new ArrayList<KV>(); headers.put(field, list); } list.add(kv); } } public Iterator<String> getNames() { return headers.keySet().iterator(); } public String[] getValues(String header) { synchronized (this) { List<KV> kvList = (List<KV>)headers.get(header); if (kvList == null) { return null; } String[] out = new String[kvList.size()]; for (int t = 0; t < kvList.size(); t++) { out[t] = ((KV)kvList.get(t)).value; } return out; } } public boolean isEmpty() { synchronized (this) { return headers.isEmpty(); } } public boolean containsKey(String s) { return headers.containsKey(s); } public Iterator<String> iterator() { return headers.keySet().iterator(); } public void dumpHeaders(OutputStream outputStream) throws IOException { for (Iterator it = headersAsPresented.iterator(); it.hasNext();) { outputStream.write(Strings.toUTF8ByteArray(it.next().toString())); outputStream.write('\r'); outputStream.write('\n'); } } private class KV { public final String key; public final String value; public KV(String key, String value) { this.key = key; this.value = value; } public KV(KV kv) { this.key = kv.key; this.value = kv.value; } } }