package org.maachang.comet.httpd.engine ;

import javax.script.ScriptException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.comet.ServiceDef;
import org.maachang.comet.State;
import org.maachang.comet.conf.HttpdAccessConfig;
import org.maachang.comet.conf.IniFile;
import org.maachang.comet.httpd.HttpdErrorDef;
import org.maachang.comet.httpd.HttpdExecutionManager;
import org.maachang.comet.httpd.HttpdHeaders;
import org.maachang.comet.httpd.HttpdRequest;
import org.maachang.comet.httpd.HttpdResponse;
import org.maachang.comet.httpd.HttpdStateException;
import org.maachang.comet.httpd.engine.auth.HttpdAuthBasic;
import org.maachang.comet.httpd.engine.auth.HttpdAuthDigest;
import org.maachang.comet.httpd.engine.auth.HttpdAuthElement;
import org.maachang.comet.httpd.engine.auth.HttpdAuthManager;
import org.maachang.comet.httpd.engine.auth.HttpdAuthUsers;
import org.maachang.comet.httpd.engine.script.BaseScriptException;
import org.maachang.comet.httpd.engine.script.scripts.ScriptManager;
import org.maachang.comet.net.HttpdHeadersImpl;
import org.maachang.comet.net.HttpdRequestImpl;
import org.maachang.comet.net.nio.ConnectionInfo;
import org.maachang.comet.net.nio.HttpReceiveCallback;
import org.maachang.conf.ConvIniParam;
import org.maachang.manager.GlobalManager;

/**
 * HTTPD受信実行.
 * 
 * @version 2007/08/29
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
class HttpdExecutionReceive implements HttpReceiveCallback {
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( HttpdExecutionReceive.class ) ;
    
    /**
     * アクセスコンフィグ.
     */
    private HttpdAccessConfig accessControll = new HttpdAccessConfig() ;
    
    /**
     * コンストラクタ.
     */
    public HttpdExecutionReceive() {
        try {
            accessControll.open() ;
        } catch( Exception e ) {
            LOG.error( "アクセスコンフィグオープンエラー",e ) ;
        }
    }
    
    /**
     * 実行処理.
     * <BR><BR>
     * 受信実行処理を行います.
     * <BR>
     * @param conn コネクションオブジェクトを設定します.
     * @param seqId シーケンスIDを設定します.
     * @param request 対象のリクエスト情報を設定します.
     * @exception Exception 例外.
     */
    public void execution( ConnectionInfo conn,int seqId,HttpdRequest request )
        throws Exception {
        if( conn == null || conn.isUse() == false ) {
            return ;
        }
        try {
            // ソケットから、リクエスト情報を取得.
            if( request == null ) {
                conn.destroy() ;
                return ;
            }
            // 指定URLがディレクトリ指定である場合の処理.
            addRequestURLByDirector( request ) ;
            
            // URLとリモートアドレスを取得.
            ( ( HttpdRequestImpl )request ).setRemote( conn.getInetAddress().getHostAddress(),conn.getPort() ) ;
            
            // アクセス条件に対するログ出力.
            if( LOG.isInfoEnabled() ) {
                LOG.info( new StringBuilder().append( "id:" ).append( seqId ).append( "[" ).
                    append( request.getRemoteAddress() ).append( ":" ).append( request.getRemotePort() ).
                    append( "]-" ).
                    append( "(" ).append( request.getMethod() ).
                    append( ")-url:" ).append( request.getUrlPath() ).
                    append( " [" ).append( request.getHeader().getHeader( HttpdDef.VALUE_CLIENT_NAME ) ).append( "]" ).
                    toString() ) ;
            }
            
            // GZIP許可フラグをリクエストに設定.
            putGzip( request ) ;
            
            // 現在のステータスを取得.
            int nowState = State.getInstance().state() ;
            
            // ステータスが、正常以外の場合.
            if( nowState != State.SUCCESS ) {
                // 計画停止の場合は、メンテナンス中にする.
                if( nowState == State.PLAN_STOP ) {
                    try {
                        WriteError.output( request,null,HttpdErrorDef.HTTP11_500,
                            "ただいま、メンテナンス中です." ) ;
                    } catch( Exception ee ) {
                    }
                }
                // 計画停止以外の場合は、ステータス名を出力.
                else {
                    try {
                        WriteError.output( request,null,HttpdErrorDef.HTTP11_503,
                            "停止中...サーバステータス(state:"+State.getInstance().toString( nowState )+")." ) ;
                    } catch( Exception ee ) {
                    }
                }
                return ;
            }
            // 不正なURLが指定された場合.
            if( HttpdDef.isSeePath( request.getUrlPath() ) == false ) {
                try {
                    WriteError.output( request,null,HttpdErrorDef.HTTP11_403,"指定URLは不正です" ) ;
                } catch( Exception ee ) {
                }
                return ;
            }
            
            // アクセス制御.
            if( accessControll.isMask( request.getUrlPath(),request.getRemoteAddress() ) == false ) {
                LOG.warn( new StringBuilder().append( "id:" ).append( seqId ).
                    append( "アクセス違反を検知" ).toString() ) ;
                // アクセス制御エラーの場合.
                WriteError.output( request,null,HttpdErrorDef.HTTP11_403,"許可されていません" ) ;
            }
            else {
                // アクセス制御正常処理.
                String method = request.getMethod() ;
                // POST及びGETの場合.
                if( HttpdDef.METHOD_GET.equals( method ) || HttpdDef.METHOD_POST.equals( method ) ) {
                    execPostOrGet( method,request,seqId ) ;
                }
                // それ以外のメソッド.
                else {
                    execNotPostAndGet( method,request ) ;
                }
            }
        } catch( Exception e ) {
            LOG.warn( "error",e ) ;
            if( conn != null ) {
                conn.destroy() ;
            }
            throw e ;
        } catch( Error er ) {
            LOG.warn( "error",er ) ;
            if( conn != null ) {
                conn.destroy() ;
            }
            throw er ;
        }
    }
    
    /**
     * POST.GET以外の処理.
     */
    private void execNotPostAndGet( String method,HttpdRequest request )
        throws Exception {
        if( HttpdDef.METHOD_HEAD.equals( method ) ) {
            executionHead( request ) ;
        }
        else if( HttpdDef.METHOD_OPTIONS.equals( method ) ) {
            executionOptions( request ) ;
        }
        else {
            executionNoSupprtMethd( request ) ;
        }
        closeConnection( request.getConnectionInfo() ) ;
    }
    
    /**
     * Head処理.
     */
    private void executionHead( HttpdRequest request )
        throws Exception {
        int state = -1 ;
        String authHeader = null ;
        HttpdAuthManager auth = ( HttpdAuthManager )GlobalManager.getValue(
            ServiceDef.MANAGER_BY_AUTH ) ;
        HttpdAuthElement emt = null ;
        // 認証条件が存在する場合.
        if( auth != null && ( emt = auth.getElement( request.getUrlPath() ) ) != null ) {
            HttpdAuthUsers users = ( HttpdAuthUsers )GlobalManager.getValue(
                ServiceDef.MANAGER_BY_USERS ) ;
            // Basic認証の場合.
            if( emt.getAuthType() == HttpdAuthElement.AUTH_BY_BASIC ) {
                // 既にBasic認証内容が存在し、間違った結果の場合.
                if( HttpdAuthBasic.isAuth( request,emt,users,request.getHeader() ) == false ) {
                    authHeader = HttpdAuthBasic.getSendAuth( emt ) ;
                    state = HttpdErrorDef.HTTP11_401 ;
                }
            }
            // Digest認証の場合.
            else if( emt.getAuthType() == HttpdAuthElement.AUTH_BY_DIGEST ) {
                // 既にDigest認証内容が存在し、間違った結果の場合.
                if( HttpdAuthDigest.isAuth( request,emt,users,request.getHeader() ) == false ) {
                    authHeader = HttpdAuthDigest.getSendAuth( emt ) ;
                    state = HttpdErrorDef.HTTP11_401 ;
                }
            }
        }
        long last = -1L ;
        if( state == -1 ) {
            boolean pub = false ;
            boolean sc = false ;
            HttpdExecutionManager pubMan = ( HttpdExecutionManager )GlobalManager.getValue(
                ServiceDef.MANAGER_BY_PUBLIC_PAGE ) ;
            HttpdExecutionManager scMan = ( HttpdExecutionManager )GlobalManager.getValue(
                ServiceDef.MANAGER_BY_APPLICATON_PAGE ) ;
            // publicが存在するかチェック.
            last = pubMan.getFileTime( request.getUrlPath() ) ;
            if( last == -1L ) {
                // applicationが存在するかチェック.
                last = scMan.getFileTime( request.getUrlPath() ) ;
                if( last != -1L ) {
                    sc = true ;
                }
            }
            else {
                pub = true ;
            }
            // 指定URLの内容が存在しない場合.
            if( last == -1L ) {
                state = HttpdErrorDef.HTTP11_404 ;
            }
            // 読み込み可能かチェック.
            if( ( pub == true && pubMan.isRead( request.getUrlPath() ) == false ) ||
                ( sc == true && scMan.isRead( request.getUrlPath() ) == false ) ) {
                state = HttpdErrorDef.HTTP11_403 ;
            }
            else {
                state = HttpdErrorDef.HTTP11_200 ;
                // キャッシュ範囲内の場合.
                if( HttpdUtil.isClientCache( request.getHeader(),last ) == true ) {
                    state = HttpdErrorDef.HTTP11_304 ;
                }
            }
        }
        // ヘッダ送信.
        HttpdHeaders header = null ;
        if( authHeader != null ) {
            header = new HttpdHeadersImpl() ;
            header.addHeader( HttpdDef.VALUE_START_AUTH,authHeader ) ;
        }
        returnHeader( request,state,last,-1L,-1,header ) ;
    }
    
    /**
     * Options処理.
     */
    private void executionOptions( HttpdRequest request )
        throws Exception {
        HttpdResponse res = HttpdResponseInstance.createResponse( request,request.getUrlPath(),
            request.getKeepAliveTimeout(),request.getKeepAliveCount() ) ;
        res.destroy() ;
    }
    
    /**
     * サポート外のmethod処理.
     */
    private void executionNoSupprtMethd( HttpdRequest request )
        throws Exception {
        WriteError.output( request,null,HttpdErrorDef.HTTP11_501,null ) ;
    }
    
    /**
     * POST.GET処理.
     */
    private void execPostOrGet( String method,HttpdRequest request,int seqId )
        throws Exception {
        try {
            executionTargetURL( request ) ;
        } catch( BaseScriptException se ) {
            // スクリプト処理エラー.
            ScriptException sc = se.getScriptException() ;
            LOG.error( "## error(id:" + seqId + ")-(url:" + request.getUrlPath() + ")",sc ) ;
            StringBuilder buf = new StringBuilder() ;
            buf.append( sc.getMessage() ).append( "<BR><BR>" ) ;
            if( se.getErrorScriptInfo() != null ) {
                buf.append( se.getErrorScriptInfo() ) ;
            }
            //buf.append( "&nbsp;行:" ).append( se.getLineNumber() ).append( "<BR>" ) ;
            //buf.append( "&nbsp;列:" ).append( se.getColumnNumber() ).append( "<BR>" ) ;
            //buf.append( "&nbsp;file:" ).append( se.getFileName() ) ;
            String msg = buf.toString() ;
            buf = null ;
            // HTTPエラーページを渡す.
            try {
                WriteError.output( request,null,HttpdErrorDef.HTTP11_500,msg ) ;
            } catch( Exception ee ) {
            }
        } catch( HttpdStateException hs ) {
            // 処理エラー.
            if( hs.getState() >= 500 ) {
                if( request != null ) {
                    LOG.error( "url:(id:" + seqId + "):" + request.getUrlPath() + " - status("+hs.getState()+") error",hs ) ;
                }
            }
            else {
                if( request != null ) {
                    LOG.warn( "url:(id:" + seqId + ")" + request.getUrlPath() + " - status("+hs.getState()+") warning",hs ) ;
                }
            }
            // HTTPエラーページを渡す.
            try {
                WriteError.output( request,null,hs.getState(),hs.getErrorMessage() ) ;
            } catch( Exception ee ) {
            }
        } catch( Exception e ) {
            // 処理エラー.
            LOG.error( "error(id:" + seqId + "):",e ) ;
            // その他エラー内容を渡す.
            try {
                WriteError.output( request,null,HttpdErrorDef.HTTP11_500,e.getMessage() ) ;
            } catch( Exception ee ) {
            }
        }
    }
    
    /**
     * ターゲットURL内容を処理.
     */
    private void executionTargetURL( HttpdRequest request )
        throws Exception {
        HttpdResponse resp = null ;
        try {
            // 画像管理情報を出力する場合.
            if( request.getUrlPath().startsWith( ImageManagerReader.HEAD_IMAGE_MANAGER_URL ) ) {
                // 指定画像情報を取得.
                ImageManagerReader im = new ImageManagerReader( request ) ;
                
                // リクエストでキャッシュヘッダが付加されている場合.
                if( HttpdUtil.isClientCache( request.getHeader(),im.getLastTime() ) == true ) {
                    returnHeader( request,HttpdErrorDef.HTTP11_304,
                        im.getLastTime(),request.getKeepAliveTimeout(),request.getKeepAliveCount(),null ) ;
                }
                // レスポンスが送信可能な場合.
                else {
                    resp = im.getResponse( request ) ;
                }
            }
            // 通常のURL.
            else {
                boolean pub = false ;
                boolean sc = false ;
                HttpdExecutionManager pubMan = ( HttpdExecutionManager )GlobalManager.getValue(
                    ServiceDef.MANAGER_BY_PUBLIC_PAGE ) ;
                HttpdExecutionManager scMan = ( HttpdExecutionManager )GlobalManager.getValue(
                    ServiceDef.MANAGER_BY_APPLICATON_PAGE ) ;
                
                // 指定パスを取得.
                String path = request.getUrlPath() ;
                
                // 指定URLの内容が存在するかチェック.
                if( pubMan.isPath( path ) == true &&
                    path.endsWith( ScriptManager.SCRIPT_PLUS ) == false ) {
                    pub = true ;
                }
                else if( scMan.isPath( path ) == true ) {
                    sc = true ;
                }
                // どれかの内容が取得可能な場合.
                if( pub == true || sc == true ) {
                    // 読み込み可能で無い場合.
                    if( ( pub == true && pubMan.isRead( path ) == false ) ||
                        ( sc == true && scMan.isRead( path ) == false ) ) {
                        throw new HttpdStateException( HttpdErrorDef.HTTP11_403 ) ;
                    }
                }
                // 情報が存在しない場合.
                else {
                    throw new HttpdStateException( HttpdErrorDef.HTTP11_404,
                        "指定パス[" + request.getUrlPath() + "]の内容は存在しません" ) ;
                }
                
                HttpdExecutionManager man = null ;
                
                // publicが読み込み可能な場合.
                if( pub == true ) {
                    man = pubMan ;
                    sc = false ;
                }
                // scriptが読み込み可能な場合.
                else if( sc == true ) {
                    man = scMan ;
                    pub = false ;
                }
                
                // スクリプト読み込みの場合の認証処理.
                if( sc == true && isAuth( request ) == true ) {
                    return ;
                }
                // 最終更新時間を取得.
                long last = man.getFileTime( path ) ;
                
                //////////////////
                // レスポンス処理.
                //////////////////
                
                // リクエストでキャッシュヘッダが付加されている場合.
                if( HttpdUtil.isClientCache( request.getHeader(),last ) == true ) {
                    returnHeader( request,HttpdErrorDef.HTTP11_304,
                        last,request.getKeepAliveTimeout(),request.getKeepAliveCount(),null ) ;
                }
                // レスポンスが送信可能な場合.
                else {
                    resp = man.get( request ) ;
                }
            }
            
            // レスポンス終了処理.
            if( resp != null ) {
                resp.flush() ;
                resp.destroy() ;
                resp = null ;
            }
            
        } finally {
            // レスポンスが存在する場合.
            if( resp != null ) {
                resp.flush() ;
                resp.destroy() ;
            }
        }
    }
    
    /**
     * ヘッダ条件のみを送付.
     */
    private void returnHeader( HttpdRequest request,int state,long date,long timeout,int count,HttpdHeaders header )
        throws Exception {
        HttpdResponse res = HttpdResponseInstance.createResponse( request,request.getUrlPath(),state,
            request.getKeepAliveTimeout(),request.getKeepAliveCount() ) ;
        if( date == -1L ) {
            res.getHeader().setHeader( "Date",HttpdTimestamp.getTimestamp( 0L ) ) ;
        }
        else {
            res.getHeader().setHeader( "Date",HttpdTimestamp.getTimestamp( date ) ) ;
        }
        res.getHeader().setHeader( HttpdDef.VALUE_CONTENT_LENGTH,String.valueOf( 0 ) ) ;
        res.setHttpClose( false ) ;
        res.getHeader().add( header ) ;
        res.destroy() ;
    }
    
    /**
     * 認証処理.
     */
    private boolean isAuth( HttpdRequest request )
        throws Exception {
        String authHeader = null ;
        HttpdAuthManager auth = ( HttpdAuthManager )GlobalManager.getValue(
            ServiceDef.MANAGER_BY_AUTH ) ;
        HttpdAuthElement emt = null ;
        // 認証条件が存在する場合.
        if( auth != null && ( emt = auth.getElement( request.getUrlPath() ) ) != null ) {
            HttpdAuthUsers users = ( HttpdAuthUsers )GlobalManager.getValue(
                ServiceDef.MANAGER_BY_USERS ) ;
            // Basic認証の場合.
            if( emt.getAuthType() == HttpdAuthElement.AUTH_BY_BASIC ) {
                // 既にBasic認証内容が存在し、間違った結果の場合.
                if( HttpdAuthBasic.isAuth( request,emt,users,request.getHeader() ) == false ) {
                    authHeader = HttpdAuthBasic.getSendAuth( emt ) ;
                }
            }
            // Digest認証の場合.
            else if( emt.getAuthType() == HttpdAuthElement.AUTH_BY_DIGEST ) {
                // 既にDigest認証内容が存在し、間違った結果の場合.
                if( HttpdAuthDigest.isAuth( request,emt,users,request.getHeader() ) == false ) {
                    authHeader = HttpdAuthDigest.getSendAuth( emt ) ;
                }
            }
        }
        // 認証条件が存在する場合.
        if( authHeader != null  ) {
            HttpdHeaders addHeader = new HttpdHeadersImpl() ;
            addHeader.addHeader( HttpdDef.VALUE_START_AUTH,authHeader ) ;
            WriteError.output( request,addHeader,HttpdErrorDef.HTTP11_401,null ) ;
            return true ;
        }
        else {
            return false ;
        }
    }
    
    /**
     * GZIP許可フラグを設定.
     */
    private static final void putGzip( HttpdRequest req ) {
        IniFile config = ( IniFile )GlobalManager.getValue( ServiceDef.MANAGER_BY_CONFIG ) ;
        boolean flag = ConvIniParam.getBoolean( config.get( "server","gzip",0 ) ) ;
        req.setGzip( flag ) ;
    }
    
    /**
     * ソケットクローズ.
     */
    private void closeConnection( ConnectionInfo conn ) {
        if( conn != null ) {
            conn.destroy() ;
        }
    }
    
    /**
     * リクエストURLがディレクトリ指定である場合の付加処理.
     */
    private void addRequestURLByDirector( HttpdRequest request ) throws Exception {
        String path = request.getUrlPath() ;
        if( path.endsWith( "/" ) ) {
            return ;
        }
        HttpdExecutionManager man = ( HttpdExecutionManager )GlobalManager.getValue(
            ServiceDef.MANAGER_BY_PUBLIC_PAGE ) ;
        if( man.isDirectory( path ) ) {
            request.setUrlPath( path+"/" ) ;
            return ;
        }
        man = ( HttpdExecutionManager )GlobalManager.getValue(
            ServiceDef.MANAGER_BY_APPLICATON_PAGE ) ;
        if( man.isDirectory( path ) ) {
            request.setUrlPath( path+"/" ) ;
            return ;
        }
    }
}
