/*
 * @(#)HttpFileUpload.java
 *
 * Copyright (c) 2001-2002 JScape
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JScape. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JScape.
 */
package com.jscape.inet.http;

import com.jscape.inet.mime.MimeException;
import com.jscape.inet.mime.MimeHeader;
import com.jscape.inet.mime.MimeHeaderAttr;
import com.jscape.inet.mime.MimeMessage;

import javax.activation.MimeType;
import javax.activation.MimeTypeParseException;
import javax.activation.MimetypesFileTypeMap;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;


/**
 * Implements methods for emulating a web browser and performing HTTP file upload
 * based on specifications defined in RFC 1867.
 * <p>
 * Example Usage:
 * </p>
 * <P>
 * <pre>
 * Http http = new Http();
 * HttpFileUpload request = new HttpFileUpload("http://www.myserver.com/cgi-bin/upload.cgi");
 * request.addParameter("name", "John Doe");
 * request.setFile("picture", "joe.gif", new File("/home/joe/joe.gif");
 * HttpResponse response = http.getResponse(request);
 * </pre>
 * </p>
 */
@SuppressWarnings("unchecked")
public class HttpFileUpload
        extends HttpRequest {

    /**
     * Straming enabled
     */
    private boolean streamingEnabled = false;

    /**
     * Place holder string for file data.
     */
    public static final String PLACEHOLDER = "[PlaceHolder]";


    /**
     * Constructs a new <code>HttpFileUpload</code> instance.
     *
     * @param url the URL to post the file upload to
     *
     * @throws HttpException if a MIME related parsing error occurs
     */
    public HttpFileUpload(String url)
            throws HttpException {
        super(url, "POST");
        try {
            setContentType("multipart/form-data");
        } catch (MimeException me) {
            throw new HttpException(me.getMessage(), me);
        }
    }

    /**
     * Adds a query parameter to <code>HttpFileUpload</code>. HTTP query
     * parameters are typically used in submitting form data for processing by
     * a CGI or servlet resident on the HTTP server. Names of query parameters
     * need not be unique.
     *
     * @param name  the name of the parameter to add.
     * @param value the value of the parameter to add.
     */
    public void addParameter(String name, String value) {
        try {
            MimeMessage msg = new MimeMessage();
            MimeHeader disposition = new MimeHeader("Content-Disposition",
                                                    "form-data");
            disposition.setFolding(false);


            if (name.length() > 0) {
                MimeHeaderAttr parameter = new MimeHeaderAttr("name",
                                                              "\"" + name + "\"");
                disposition.addAttribute(parameter);
            }

            msg.addReplaceHeader(disposition);
            msg.setBody(value);
            addHttpFileUploadPart(msg);
        } catch (MimeException me) {
            // todo: ignore?
        } catch (HttpException me) {
            me.printStackTrace();
        }
    }

    /**
     * Adds a query parameter to <code>HttpFileUpload</code>. HTTP query
     * parameters are typically used in submitting form data for processing by
     * a CGI or servlet resident on the HTTP server. Names of query parameters
     * need not be unique.
     *
     * @param parameter the <code>HttpParameter</code> to add
     *
     * @see HttpParameter
     */
    public void addParameter(HttpParameter parameter) {
        addParameter(parameter.getName(), parameter.getValue());
    }

    /**
     * Streaming.
     *
     * @return boolean value for enable streaming.
     */
    public boolean isStreamingEnabled() {
        return this.streamingEnabled;
    }


    /**
     * Stream file to HTTP server instead of reading entire File object in RAM.
     *
     * @param fieldName
     * @param fileName
     * @param file
     *
     * @throws HttpException
     */
    public void streamFileToServer(String fieldName, String fileName, File file)
            throws HttpException {
        this.streamingEnabled = true;
        MimeMessage msg = null; // stores message contents

        try {
            // build up message
            msg = new MimeMessage();

            //get all the MIME-related data required to construct proper header
            MimeHeader hdrContentDisposition = new MimeHeader("Content-Disposition",
                                                              "form-data");
            hdrContentDisposition.setFolding(false);

            // build input field name attribute
            if (fieldName.length() > 0) {
                MimeHeaderAttr fieldNameAttribute = new MimeHeaderAttr("name",
                                                                       "\"" + fieldName + "\"");
                hdrContentDisposition.addAttribute(fieldNameAttribute);
            }

            // build file name attribute
            MimeHeaderAttr fileMameAttribute = new MimeHeaderAttr("filename",
                                                                  (fileName.length() == 0) ? ("\"" + file.getName() + "\"")
                                                                                           : ("\"" + fileName + "\""));
            hdrContentDisposition.addAttribute(fileMameAttribute);
            msg.addReplaceHeader(hdrContentDisposition);

            // build Content-Type header
            MimetypesFileTypeMap map = new MimetypesFileTypeMap();
            String contentType = map.getContentType(file.getName());

            if (contentType == null) {
                contentType = "application/x-unknown";
            }

            MimeHeader hdrContentType = new MimeHeader("Content-Type",
                                                       contentType);
            msg.addReplaceHeader(hdrContentType);

            // build Content-Transfer-Encoding header
            MimeType type = new MimeType(contentType);
            String primaryType = type.getPrimaryType();

            if ((primaryType != null) && primaryType.equals("text")) {
                MimeHeader hdrContentTransfer = new MimeHeader("Content-Transfer-Encoding",
                                                               "8bit");
                msg.addReplaceHeader(hdrContentTransfer);
            } else {
                MimeHeader hdrContentTransfer = new MimeHeader("Content-Transfer-Encoding",
                                                               "binary");
                msg.addReplaceHeader(hdrContentTransfer);
            }
        } catch (MimeTypeParseException mte) {
            // error determining Content-Type
            try {
                MimeHeader hdrContentTransfer = new MimeHeader("Content-Transfer-Encoding",
                                                               "8bit");
                msg.addReplaceHeader(hdrContentTransfer);
            } catch (MimeException me) {
                throw new HttpException(me.getMessage(), me);
            }
        } catch (Exception e) {
            // unknown error occured
            throw new HttpException(e.getMessage(), e);
        }

        //finaly, fill the body with the file
        msg.setBodyAsFile(file);
        addHttpFileUploadPart(msg);
    }

    /**
     * Sets the file to upload for this <code>HttpFileUpload</code>.
     *
     * @param fieldName the name of the field, as specified in HTML INPUT
     *                  TYPE=FILE tag
     * @param fileName  the value to set the MIME filename attribute to
     * @param file      the File object of the file to be added
     *
     * @throws HttpException if file is not found
     */
    public void setFile(String fieldName, String fileName, File file)
            throws HttpException {
        this.streamingEnabled = false;
        MimeMessage msg = null; // stores message contents
        byte[] data = null; // stores file data

        try (InputStream fin = Files.newInputStream(file.toPath())) {
            //read the file
            byte[] buffer = new byte[2048];
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            int b = 0;

            while ((b = fin.read(buffer)) != -1) {
                bout.write(buffer, 0, b);
            }

            data = bout.toByteArray();

            // build up message
            msg = new MimeMessage();

            //get all the MIME-related data required to construct proper header
            MimeHeader hdrContentDisposition = new MimeHeader("Content-Disposition",
                                                              "form-data");
            hdrContentDisposition.setFolding(false);

            // build input field name attribute
            if (fieldName.length() > 0) {
                MimeHeaderAttr fieldNameAttribute = new MimeHeaderAttr("name",
                                                                       "\"" + fieldName + "\"");
                hdrContentDisposition.addAttribute(fieldNameAttribute);
            }

            // build file name attribute
            MimeHeaderAttr fileMameAttribute = new MimeHeaderAttr("filename",
                                                                  (fileName.length() == 0) ? ("\"" + file.getName() + "\"")
                                                                                           : ("\"" + fileName + "\""));
            hdrContentDisposition.addAttribute(fileMameAttribute);
            msg.addReplaceHeader(hdrContentDisposition);

            // build Content-Type header
            MimetypesFileTypeMap map = new MimetypesFileTypeMap();
            String contentType = map.getContentType(file.getName());

            if (contentType == null) {
                contentType = "application/x-unknown";
            }

            MimeHeader hdrContentType = new MimeHeader("Content-Type",
                                                       contentType);
            msg.addReplaceHeader(hdrContentType);

            // build Content-Transfer-Encoding header
            MimeType type = new MimeType(contentType);
            String primaryType = type.getPrimaryType();

            if ((primaryType != null) && primaryType.equals("text")) {
                MimeHeader hdrContentTransfer = new MimeHeader("Content-Transfer-Encoding",
                                                               "8bit");
                msg.addReplaceHeader(hdrContentTransfer);
            } else {
                MimeHeader hdrContentTransfer = new MimeHeader("Content-Transfer-Encoding",
                                                               "binary");
                msg.addReplaceHeader(hdrContentTransfer);
            }
        } catch (MimeTypeParseException mte) {
            // error determining Content-Type
            try {
                MimeHeader hdrContentTransfer = new MimeHeader("Content-Transfer-Encoding",
                                                               "8bit");
                msg.addReplaceHeader(hdrContentTransfer);
            } catch (MimeException me) {
                throw new HttpException(me.getMessage(), me);
            }
        } catch (Exception e) {
            // unknown error occurred
            throw new HttpException(e.getMessage(), e);
        }

        //finaly, fill the body with the file
        msg.setBody(data);
        addHttpFileUploadPart(msg);
    }

    /**
     * Sets the file to upload for this <code>HttpFileUpload</code>.
     *
     * @param fieldName the name of the field, as specified in HTML INPUT TYPE=FILE tag
     * @param filename  the value to set the MIME filename attribute to
     * @param data      the data representing the file to upload
     *
     * @throws HttpException if file is not found
     */
    public void setFile(String fieldName, String filename, byte[] data)
            throws HttpException {
        this.streamingEnabled = false;
        MimeMessage msg = null; // stores message contents

        try {
            // build up message
            msg = new MimeMessage();

            //get all the MIME-related data required to construct proper header
            MimeHeader hdrContentDisposition = new MimeHeader("Content-Disposition",
                                                              "form-data");
            hdrContentDisposition.setFolding(false);

            // build input field name attribute
            if (fieldName.length() > 0) {
                MimeHeaderAttr fieldNameAttribute = new MimeHeaderAttr("name",
                                                                       "\"" + fieldName + "\"");
                hdrContentDisposition.addAttribute(fieldNameAttribute);
            }

            // build file name attribute
            MimeHeaderAttr fileMameAttribute = new MimeHeaderAttr("filename", "\"" + filename + "\"");
            hdrContentDisposition.addAttribute(fileMameAttribute);
            msg.addReplaceHeader(hdrContentDisposition);

            // build Content-Type header
            MimetypesFileTypeMap map = new MimetypesFileTypeMap();
            String contentType = map.getContentType(filename);

            if (contentType == null) {
                contentType = "application/x-unknown";
            }

            MimeHeader hdrContentType = new MimeHeader("Content-Type",
                                                       contentType);
            msg.addReplaceHeader(hdrContentType);

            // build Content-Transfer-Encoding header
            MimeType type = new MimeType(contentType);
            String primaryType = type.getPrimaryType();

            if ((primaryType != null) && primaryType.equals("text")) {
                MimeHeader hdrContentTransfer = new MimeHeader("Content-Transfer-Encoding",
                                                               "8bit");
                msg.addReplaceHeader(hdrContentTransfer);
            } else {
                MimeHeader hdrContentTransfer = new MimeHeader("Content-Transfer-Encoding",
                                                               "binary");
                msg.addReplaceHeader(hdrContentTransfer);
            }
        } catch (MimeTypeParseException mte) {
            // error determining Content-Type
            try {
                MimeHeader hdrContentTransfer = new MimeHeader("Content-Transfer-Encoding",
                                                               "8bit");
                msg.addReplaceHeader(hdrContentTransfer);
            } catch (MimeException me) {
                throw new HttpException(me.getMessage(), me);
            }
        } catch (Exception e) {
            // unknown error occured
            throw new HttpException(e.getMessage(), e);
        }

        //finaly, fill the body with the data
        msg.setBody(data);
        addHttpFileUploadPart(msg);
    }


    /**
     * Adds part to this <code>HttpFileUpload</code>. If not previously set
     * boundary string is generated. This method also sets Content-Type field
     * for this <code>MimeMessage</code> to multipart/form-data in case of
     * standard HttpFileUpload message. In case of mixed HttpFileUpload
     * message, Content-Type will be set to multipart/mixed when the message
     * is added to standard HttpFileUpload message.
     *
     * @param message the message to add
     *
     * @throws HttpException if a MIME related parsing error occurs
     * @see MimeMessage
     */
    private void addHttpFileUploadPart(MimeMessage message)
            throws HttpException {
        synchronized (this) {
            try {
                parts.addElement(message);

                String boundary = getBoundary();

                if (boundary == null) {
                    // generate boundary string
                    boundary = new String("___BoUnDaRy_") + Long.toString(System.currentTimeMillis());

                    MimeHeader head = new MimeHeader("Content-Type", "multipart/form-data");
                    MimeHeaderAttr attr = new MimeHeaderAttr("boundary", boundary);
                    head.addAttribute(attr);
                    addReplaceHeader(head);
                    contentType = new String("multipart/form-data");
                }
            } catch (MimeException me) {
                throw new HttpException(me.getMessage(), me);
            }
        }
    }

}
