package org.maachang.proxy.engine.net.http;

import org.maachang.proxy.engine.net.RequestInfo;
import org.maachang.util.AnalysisUtil;
import org.maachang.util.FileUtil;

/**
 * multipart/form-dataを解析.
 * 
 * @version 2008/04/14
 * @author masahito suzuki
 * @since MaachangProxy 1.03
 */
public class MultiPartForm {
    
    private static final byte[] ENTER = new byte[] {(byte) 13, (byte) 10} ;
    
    /**
     * Body情報から、multipart/form-dataを解析.
     * <BR>
     * @param req 対象のHTTPリクエストを設定します.
     * @param charset 対象のキャラクタセットを設定します.
     * @return HttpParams 格納パラメータが返されます.
     * @exception Exception 例外.
     */
    public static final HttpParams convertMultiPartForm( RequestInfo req,String charset )
        throws Exception {
        if( req.getBody() == null || req.getBody().length <= 0 ) {
            return new HttpParams() ;
        }
        if( charset == null || ( charset = charset.trim() ).length() <= 0 ) {
            charset = "UTF8" ;
        }
        HttpParams ret = null ;
        int p = 0 ;
        int b = 0 ;
        int enterLen = ENTER.length ;
        int len = req.headerSize( "Content-Type" ) ;
        String multiPartForm = null ;
        String dataType = null ;
        for( int i = 0 ; i < len ; i ++ ) {
            String contentType = req.getHeader( "Content-Type",i ) ;
            if( multiPartForm == null ) {
                int boundary = -1 ;
                if( ( boundary = contentType.indexOf( "multipart/form-data" ) ) != -1 &&
                    ( boundary = contentType.indexOf( "boundary=",boundary ) ) != -1 ) {
                    multiPartForm = contentType.substring( boundary+"boundary=".length(),contentType.length() ) ;
                }
            }
            if( dataType == null || contentType.startsWith( "application/x-www-form-urlencoded" ) == false ) {
                dataType = contentType ;
            }
        }
        // POSTに[Content-Type]が存在しない場合.
        if( dataType == null ) {
            // [application/x-www-form-urlencoded]であることにする(IEのAjax対応).
            dataType = "application/x-www-form-urlencoded" ;
        }
        // HTTPがMultipartFormの場合.
        if( multiPartForm != null ) {
            ret = new HttpParams() ;
            byte[] binary = req.getBody() ;
            String target = "--" + multiPartForm ;
            String end = target + "--" ;
            String keyName = null ;
            String fileName = null ;
            String mimeType = null ;
            boolean dataFlag = false ;
            boolean keyFlag = false ;
            int filenameLen = "; filename=\"".length() ;
            int nameLen = "; name=\"".length() ;
            for( ;; ) {
                // データ条件で無い場合.
                if( dataFlag == false ) {
                    p = AnalysisUtil.binaryIndexOf( binary,ENTER,b,binary.length ) ;
                    if( p == -1 ) {
                        break ;
                    }
                    else if( p == b ) {
                        b += enterLen ;
                        continue ;
                    }
                    String one = new String( binary,b,p-b,charset ) ;
                    b = p + enterLen ;
                    // データ開始値.
                    if( keyFlag == false ) {
                        // データの開始値の場合.
                        if( one.equals( target ) == true ) {
                            keyFlag = true ;
                        }
                        // 全てのデータ終了値の場合.
                        else if( one.equals( end ) == true ) {
                            break ;
                        }
                        // その他.
                        else {
                            throw new IllegalArgumentException( "不正な条件を検出しました" ) ;
                        }
                    }
                    // データキー名など.
                    else {
                        // データ情報の場合.
                        if( one.length() <= 0 ) {
                            dataFlag = true ;
                        }
                        // データキー名などの情報.
                        else {
                            // アップロードファイル名.
                            if( one.startsWith( "Content-Disposition:" ) ) {
                                int filePnt = one.indexOf( "; filename=\"" ) ;
                                if( filePnt != -1 ) {
                                    filePnt+= filenameLen ;
                                    String fullPath = one.substring( filePnt,one.indexOf( "\"",filePnt ) ).trim() ;
                                    fileName = FileUtil.getFileName( fullPath ).trim() ;
                                }
                            }
                            // データキー名.
                            int namePnt = one.indexOf( "; name=\"" ) ;
                            if( namePnt != -1 ) {
                                namePnt+=nameLen ;
                                keyName = one.substring( namePnt,one.indexOf( "\"",namePnt ) ).trim() ;
                            }
                            // MimeType.
                            else if( one.startsWith( "Content-Type:" ) ) {
                                mimeType = one.substring( "Content-Type:".length(),one.length() ).trim() ;
                            }
                            // その他.
                            else {
                                throw new IllegalArgumentException( "不正な条件を検出しました" ) ;
                            }
                        }
                    }
                }
                // データ情報.
                else {
                    // uploadBinary情報.
                    if( fileName != null ) {
                        p = AnalysisUtil.binaryIndexOf( binary,target,b,binary.length ) ;
                        if( p == -1 ) {
                            throw new IllegalArgumentException( "不正な条件を検出しました" ) ;
                        }
                        if( fileName == null || ( fileName = fileName.trim() ).length() <= 0 ) {
                            b = p ;
                        }
                        else {
                            len = p-(b+enterLen) ;
                            byte[] uploadBinary = new byte[ len ] ;
                            System.arraycopy( binary,b,uploadBinary,0,len ) ;
                            b = p ;
                            HttpdBinary bin = new HttpdBinary( fileName,mimeType,uploadBinary ) ;
                            ret.add( keyName,bin ) ;
                        }
                    }
                    else {
                        p = AnalysisUtil.binaryIndexOf( binary,target,b,binary.length ) ;
                        if( p == -1 ) {
                            throw new IllegalArgumentException( "不正な条件を検出しました" ) ;
                        }
                        String one = new String( binary,b,p-(b+enterLen),charset ) ;
                        b = p ;
                        ret.add( keyName,one ) ;
                    }
                    keyName = null ;
                    fileName = null ;
                    mimeType = null ;
                    dataFlag = false ;
                    keyFlag = false ;
                }
            }
        }
        // 通常のForm情報の場合.
        else if( dataType != null && dataType.startsWith( "application/x-www-form-urlencoded" ) == true ) {
            ret = AnalysisUtil.convertAnalysisBody( req,charset ) ;
        }
        return ret ;
    }
    
    /**
     * 指定リクエストがmultipart/form-dataかチェック.
     * @request req 対象のリクエスト情報を設定します.
     * @return boolean [true]の場合、multipart/form-dataです.
     */
    public static final boolean isMultiPartForm( RequestInfo req ) {
        if( req.getBody() == null || req.getBody().length <= 0 ) {
            return false ;
        }
        int len = req.headerSize( "Content-Type" ) ;
        String multiPartForm = null ;
        for( int i = 0 ; i < len ; i ++ ) {
            String contentType = req.getHeader( "Content-Type",i ) ;
            if( multiPartForm == null ) {
                int boundary = -1 ;
                if( ( boundary = contentType.indexOf( "multipart/form-data" ) ) != -1 &&
                    ( boundary = contentType.indexOf( "boundary=",boundary ) ) != -1 ) {
                    multiPartForm = contentType.substring( boundary+"boundary=".length(),contentType.length() ) ;
                    break ;
                }
            }
        }
        return multiPartForm != null ;
    }
}

