/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

//@@3RD PARTY CODE@@

// ========================================================================
// Copyright (c) 1996 Mort Bay Consulting Pty. Ltd. All rights reserved.
// Id: MultiPartRequest.java,v 1.4 2002/12/14 13:03:05 bretts Exp
// ------------------------------------------------------------------------

package com.sun.xml.bind.webapp.multipart;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.StringTokenizer;
import javax.servlet.http.HttpServletRequest;

/* ------------------------------------------------------------ */
/** Multipart Form Data request.
 * <p>
 * This class decodes the multipart/form-data stream sent by
 * a HTML form that uses a file input item.
 *
 * <p><h4>Usage</h4>
 * Each part of the form data is named from the HTML form and
 * is available either via getString(name) or getInputStream(name).
 * Furthermore the MIME parameters and filename can be requested for
 * each part.
 * <pre>
 * </pre>
 *
 * @version Id: MultiPartRequest.java,v 1.4 2002/12/14 13:03:05 bretts Exp
 * @author  Greg Wilkins
 * @author  Jim Crossley
 */
public class MultiPartRequest
{
    /* ------------------------------------------------------------ */
    HttpServletRequest _request;
    LineInput _in;
    String _boundary;
    byte[] _byteBoundary;
    Hashtable _partMap = new Hashtable(10);
    int _char=-2;
    boolean _lastPart=false;
    
    /* ------------------------------------------------------------ */
    /** Constructor. 
     * @param request The request containing a multipart/form-data
     * request
     * @exception IOException IOException
     */
    public MultiPartRequest(HttpServletRequest request)
        throws IOException
    {
        _request=request;
        String content_type = request.getHeader("Content-Type");
        if (!content_type.startsWith("multipart/form-data"))
            throw new IOException("Not multipart/form-data request");

        _in = new LineInput(request.getInputStream());
        
        // Extract boundary string
        _boundary="--"+
            value(content_type.substring(content_type.indexOf("boundary=")));
        
        _byteBoundary= (_boundary+"--").getBytes("iso-8859-1");
        
        loadAllParts();
    }
    
    /* ------------------------------------------------------------ */
    /** Get the part names.
     * @return an array of part names
     */
    public String[] getPartNames()
    {
        return (String[]) _partMap.keySet().toArray(new String[0]);
    }
    
    /* ------------------------------------------------------------ */
    /** Check if a named part is present 
     * @param name The part
     * @return true if it was included 
     */
    public boolean contains(String name)
    {
        Part part = (Part)_partMap.get(name);
        return (part!=null);
    }
    
    /** Check if a named part has non-empty data */
    public boolean has(String name) {
        Part part = (Part)_partMap.get(name);
        if(part==null)  return false;
        
        return part._data.length!=0;
    }
    
    /* ------------------------------------------------------------ */
    /** Get the data of a part as a string.
     * @param name The part name 
     * @return The part data
     */
    public String getString(String name)
    {
        Part part = (Part)_partMap.get(name);
        if (part==null)
            return null;
        return new String(part._data);
    }
    

    /* ------------------------------------------------------------ */
    /** Get the data of a part as a stream.
     * @param name The part name 
     * @return Stream providing the part data
     */
    public InputStream getInputStream(String name)
    {
        Part part = (Part)_partMap.get(name);
        if (part==null)
            return null;
        return new ByteArrayInputStream(part._data);
    }
    
    /* ------------------------------------------------------------ */
    /** Get the MIME parameters associated with a part.
     * @param name The part name 
     * @return Hashtable of parameters
     */
    public Hashtable getParams(String name)
    {
        Part part = (Part)_partMap.get(name);
        if (part==null)
            return null;
        return part._headers;
    }
    
    /* ------------------------------------------------------------ */
    /** Get any file name associated with a part.
     * @param name The part name 
     * @return The filename
     */
    public String getFilename(String name)
    {
        Part part = (Part)_partMap.get(name);
        if (part==null)
            return null;
        return part._filename;
    }
    
        
    
    /* ------------------------------------------------------------ */
    private void loadAllParts()
        throws IOException
    {
        // Get first boundary
        String line = _in.readLine();
        if (!line.equals(_boundary))
        {
            throw new IOException("Missing initial multi part boundary");
        }
        
        // Read each part
        while (!_lastPart)
        {
            // Read Part headers
            Part part = new Part();
            
            String content_disposition=null;
            while ((line=_in.readLine())!=null)
            {
                // If blank line, end of part headers
                if (line.length()==0)
                    break;

                // place part header key and value in map
                int c = line.indexOf(':',0);
                if (c>0)
                {
                    String key = line.substring(0,c).trim().toLowerCase();
                    String value = line.substring(c+1,line.length()).trim();
                    String ev = (String) part._headers.get(key);
                    part._headers.put(key,(ev!=null)?(ev+';'+value):value);
                    if (key.equals("content-disposition"))
                        content_disposition=value;
                }
            }

            // Extract content-disposition
            boolean form_data=false;
            if (content_disposition==null)
            {
                throw new IOException("Missing content-disposition");
            }
            
            StringTokenizer tok =
                new StringTokenizer(content_disposition,";");
            while (tok.hasMoreTokens())
            {
                String t = tok.nextToken().trim();
                String tl = t.toLowerCase();
                if (t.startsWith("form-data"))
                    form_data=true;
                else if (tl.startsWith("name="))
                    part._name=value(t);
                else if (tl.startsWith("filename="))
                    part._filename=value(t);
            }

            // Check disposition
            if (!form_data)
            {
                continue;
            }
            if (part._name==null || part._name.length()==0)
            {
                continue;
            }
            _partMap.put(part._name,part);
            part._data=readBytes();
        }       
    }

    /* ------------------------------------------------------------ */
    private byte[] readBytes()
        throws IOException
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        int c;
        boolean cr=false;
        boolean lf=false;
        
        // loop for all lines`
        while (true)
        {
            int b=0;
            while ((c=(_char!=-2)?_char:_in.read())!=-1)
            {
                _char=-2;

                // look for CR and/or LF
                if (c==13 || c==10)
                {
                    if (c==13) _char=_in.read();
                    break;
                }

                // look for boundary
                if (b>=0 && b<_byteBoundary.length && c==_byteBoundary[b])
                    b++;
                else
                {
                    // this is not a boundary
                    if (cr) baos.write(13);
                    if (lf) baos.write(10);
                    cr=lf=false;
                    
                    if (b>0)
                        baos.write(_byteBoundary,0,b);
                    b=-1;
                  
                    baos.write(c);
                }
            }

            // check partial boundary
            if ((b>0 && b<_byteBoundary.length-2) ||
                (b==_byteBoundary.length-1))
            {
                baos.write(_byteBoundary,0,b);
                b=-1;
            }
            
            // boundary match
            if (b>0 || c==-1)
            {
                if (b==_byteBoundary.length)
                    _lastPart=true;
                if (_char==10) _char=-2;
                break;
            }
            
            // handle CR LF
            if (cr) baos.write(13);
            if (lf) baos.write(10);
            cr=(c==13);
            lf=(c==10 || _char==10);
            if (_char==10) _char=-2;  
        }
        return baos.toByteArray();
    }
    
    
    /* ------------------------------------------------------------ */
    private String value(String nameEqualsValue)
    {   
        String value =
            nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim();
        
        int i=value.indexOf(';');
        if (i>0)
            value=value.substring(0,i);
        if (value.startsWith("\""))
        {
            value=value.substring(1,value.indexOf('"',1));
        }
        
        else
        {
            i=value.indexOf(' ');
            if (i>0)
                value=value.substring(0,i);
        }
        return value;
    }
    
    /* ------------------------------------------------------------ */
    private class Part
    {
        String _name=null;
        String _filename=null;
        Hashtable _headers= new Hashtable(10);
        byte[] _data=null;
    }    
};

