package org.maachang.proxy.engine;

import java.io.BufferedOutputStream;
import java.io.OutputStream;
import java.net.InetAddress;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.proxy.engine.admin.AdminException;
import org.maachang.proxy.engine.admin.AdminManager;
import org.maachang.proxy.engine.conf.AccessManager;
import org.maachang.proxy.engine.conf.MaachangProxyConfig;
import org.maachang.proxy.engine.conf.RefusalAddressManager;
import org.maachang.proxy.engine.error.ErrorDef;
import org.maachang.proxy.engine.error.ErrorPageManager;
import org.maachang.proxy.engine.mobile.ConvertMobile;
import org.maachang.proxy.engine.mobile.MobileInfo;
import org.maachang.proxy.engine.mobile.carrier.CarrierConfig;
import org.maachang.proxy.engine.mobile.carrier.MobileData;
import org.maachang.proxy.engine.mobile.emoji.ConvertEmoji;
import org.maachang.proxy.engine.mobile.emoji.EmojiConfig;
import org.maachang.proxy.engine.mobile.emoji.image.EmojiImageCache;
import org.maachang.proxy.engine.net.RequestInfo;
import org.maachang.proxy.engine.net.ResponseInfo;
import org.maachang.proxy.engine.net.http.ConnectionInfo;
import org.maachang.proxy.engine.net.http.Cookie;
import org.maachang.proxy.engine.net.http.HttpCallbackReceive;
import org.maachang.proxy.engine.net.http.HttpdTimestamp;
import org.maachang.proxy.engine.net.server.ExpireServerConnectException;
import org.maachang.proxy.engine.net.server.HttpConnector;
import org.maachang.proxy.engine.net.server.HttpPoolingConnector;
import org.maachang.proxy.engine.net.server.ProxyInfo;
import org.maachang.proxy.engine.net.server.ProxyManager;
import org.maachang.util.AnalysisUtil;
import org.maachang.util.CharsetMask;
import org.maachang.util.FileUtil;
import org.maachang.util.StringUtil;

/**
 * Proxy用コールバック.
 * 
 * @version 2007/11/17
 * @author  masahito suzuki
 * @since MaachangProxy 1.00
 */
class ProxyReceiveCallback implements HttpCallbackReceive {
    private static final Log LOG = LogFactory.getLog( ProxyReceiveCallback.class ) ;
    private static final long WAIT_TIME = 15L ;
    
    /** Proxyマネージャ. */
    private ProxyManager manager = null ;
    
    /** 禁止アクセスアドレス管理. */
    private RefusalAddressManager refusal = null ;
    
    /** MaachangProxyコンフィグ. */
    private MaachangProxyConfig maachangProxyConfig = null ;
    
    /** アクセスマネージャ. */
    private AccessManager accessManager = null ;
    
    /** 管理画面管理オブジェクト. */
    private AdminManager admin = null ;
    
    /** 携帯キャリア情報. */
    private CarrierConfig carrierConfig = null ;
    
    /** 絵文字変換情報. */
    private EmojiConfig emojiConfig = null ;
    
    /**
     * コンストラクタ.
     */
    private ProxyReceiveCallback() {
    }
    
    /**
     * コンストラクタ.
     * @param manager 対象のProxyManagerを設定します.
     * @param refusal 対象の禁止アドレス管理を設定します.
     * @param accessManager 対象のアクセスマネージャを設定します.
     * @exception Exception 例外.
     */
    public ProxyReceiveCallback( ProxyManager manager,RefusalAddressManager refusal,
        AccessManager accessManager ) throws Exception {
        
        if( manager == null || manager.isUse() == false ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        this.manager = manager ;
        this.refusal = refusal ;
        this.accessManager = accessManager ;
        this.admin = new AdminManager() ;
        this.carrierConfig = new CarrierConfig() ;
        this.emojiConfig = new EmojiConfig() ;
        this.maachangProxyConfig = new MaachangProxyConfig() ;
        
    }
    
    /**
     * 実行処理.
     * <BR><BR>
     * 受信実行処理を行います.
     * <BR>
     * @param conn コネクションオブジェクトを設定します.
     * @param seqId シーケンスIDを設定します.
     * @param request 対象のリクエスト情報を設定します.
     * @exception Exception 例外.
     */
    public void execution( ConnectionInfo conn,int seqId,RequestInfo request )
        throws Exception {
        
        // ホスト名を取得.
        String host = request.getHeader( "Host",0 ) ;
        String url = request.getUrl() ;
        if( LOG.isInfoEnabled() ) {
            LOG.info( "## access-hostName("+seqId+"):" + host + url ) ;
        }
        
        InetAddress connectAddress = conn.getSocket().socket().getInetAddress() ;
        String userAgent = request.getHeader( "User-Agent",0 ) ;
        
        // リクエストヘッダを調整.
        smartRequest( request ) ;
        
        // 携帯アクセスデータを取得.
        MobileInfo mobileInfo = getMobileInfo( request,seqId ) ;
        
        // 管理者アクセス.
        if( adminAccess( request,conn,seqId,mobileInfo,host,url ) ) {
            return ;
        }
        // 拒否アドレスかチェック.
        if( refusal.isRefusal( connectAddress ) ) {
            LOG.warn( "拒否アドレスからのアクセスを検知[" + connectAddress.getHostAddress() + "]" ) ;
            conn.setCloseFlag( true ) ;
            return ;
        }
        // ユーザエージェントが存在しないアクセスは拒否.
        if( userAgent == null || ( userAgent = userAgent.trim() ).length() <= 0 ) {
            LOG.warn( "ユーザエージェントなしのアクセスを検知[" + connectAddress.getHostAddress() + "]" ) ;
            conn.setCloseFlag( true ) ;
            return ;
        }
        // エラーページ経由からの外部ファイル読み込みを要求するURLの場合.
        if( errorPageToIncludeData( request,conn,mobileInfo,host,url ) ) {
            return ;
        }
        // 絵文字イメージキャッシュデータを要求するURLの場合.
        if( emojiImageData( request,conn,mobileInfo,host,url ) ) {
            return ;
        }
        
        // 指定Host名に対する接続先情報を取得.
        ProxyInfo info = manager.get( request ) ;
        if( info == null ) {
            // 指定コネクションが管理情報に存在しない場合.
            sendErrorMessage( new StateException( host,ErrorDef.HTTP11_404,"指定Host["+host+"]は存在しません" ),
                conn,host+url,mobileInfo ) ;
            return ;
        }
        // 対象Proxy接続条件に対して、接続指定外の条件の場合.
        if( noAccessClient( info,mobileInfo.getMobileData() ) == true ) {
            LOG.warn( "拒否ユーザエージェントのアクセスを検知[" + userAgent + "]" ) ;
            conn.setCloseFlag( true ) ;
            return ;
        }
        
        // リクエストHostに現在のシーケンスIDを付加.
        request.putHeader( "X-Sequence-Id",String.valueOf( seqId ) ) ;
        
        // POST及び、GET内容の絵文字を変換.
        ConvertEmoji.convertRequestBody( request,mobileInfo.getEmojiConfig(),mobileInfo.getMobileData() ) ;
        
        // セッションIDを取得.
        Cookie.getSessionId( info,request ) ;
        
        // 接続条件.
        HttpPoolingConnector pool = null ;
        HttpConnector connector = null ;
        
        // 対象サーバに接続.
        try {
            // プーリングコネクションを取得.
            pool = info.getHttpPoolingConnector() ;
            
            // 接続先のホスト名に変換.
            changeConnectionHostName( request,info ) ;
            
            // 携帯アクセスの場合、request付与データをサーバ側charsetで変換.
            if( mobileInfo.getMobileData() != null ) {
                convertGetPostDataByCharset( request,info,mobileInfo.getMobileData(),mobileInfo.getEmojiConfig() ) ;
            }
            
            // 通信処理.
            for( ;; ) {
                // １つのサーバコネクションを取得.
                connector = pool.getConnector( conn ) ;
                try {
                    // 送受信処理.
                    sendReceive( request,conn,info,pool,connector,mobileInfo,connectAddress,host ) ;
                    break ;
                } catch( ExpireServerConnectException ee ) {
                    // Expire発生の場合は、リコネクション.
                    try {
                        connector.destroy() ;
                    } catch( Exception e ) {
                    }
                }
            }
            
        } catch( AdminException ae ) {
            sendErrorMessage( ae,conn,host+url,mobileInfo ) ;
        } catch( StateException se ) {
            sendErrorMessage( se,conn,host+url,mobileInfo ) ;
        } catch( Exception e ) {
            LOG.error( "## error",e ) ;
            sendErrorMessage( e,conn,host+url,mobileInfo ) ;
        }
    }
    
    /**
     * HTTPリクエストを最適化.
     */
    private static final void smartRequest( RequestInfo req ) {
        if( req.getHeader( "Transfer-Encoding",0 ) != null ) {
            if( req.getBody() != null ) {
                req.putHeader( "Content-Length",String.valueOf( req.getBody().length ) ) ;
            }
            req.removeHeader( "Transfer-Encoding" ) ;
        }
    }
    
    /**
     * 携帯アクセスデータを取得.
     */
    private MobileInfo getMobileInfo( RequestInfo request,long seqId ) throws Exception {
        // 携帯機種アクセス情報を取得して、リクエストヘッダに情報付加.
        String userAgent = request.getHeader( "User-Agent",0 ) ;
        MobileData mobileData = carrierConfig.getAgent( userAgent ) ;
        if( mobileData == null ) {
            request.putHeader( "X-Mobile-Carrier","pc" ) ;
            if( LOG.isInfoEnabled() ) {
                LOG.info( "access-client("+seqId+"):pc" ) ;
            }
        }
        else {
            request.putHeader( "X-Mobile-Carrier",mobileData.getCarrier() ) ;
            request.putHeader( "X-Moblie-Code",mobileData.getCode() ) ;
            request.putHeader( "X-Moblie-Type",mobileData.getType() ) ;
            request.putHeader( "X-Mobile-X",String.valueOf( mobileData.getX() ) ) ;
            request.putHeader( "X-Mobile-Y",String.valueOf( mobileData.getY() ) ) ;
            request.putHeader( "X-Mobile-Jpeg",String.valueOf( mobileData.isJpegFlag() ) ) ;
            request.putHeader( "X-Mobile-Gif",String.valueOf( mobileData.isGifFlag() ) ) ;
            request.putHeader( "X-Mobile-Png",String.valueOf( mobileData.isPngFlag() ) ) ;
            request.putHeader( "X-Mobile-Flash",String.valueOf( mobileData.isFlashFlag() ) ) ;
            if( LOG.isInfoEnabled() ) {
                LOG.info( "access-client("+seqId+"):mobile[" + mobileData + "]" ) ;
            }
        }
        return new MobileInfo( request,emojiConfig,mobileData ) ;
    }
    
    /**
     * 管理者アクセス処理.
     */
    private boolean adminAccess( RequestInfo request,ConnectionInfo conn,int seqId,MobileInfo mobileInfo,String host,String url )
        throws Exception {
        InetAddress connAddr = conn.getSocket().socket().getInetAddress() ;
        // 管理画面アクセスであるかチェック.
        if( maachangProxyConfig.isAdminUrl( host,request.getUrl() ) &&
            maachangProxyConfig.isAdmin( connAddr ) ) {
            if( LOG.isInfoEnabled() ) {
                LOG.info( "管理者画面[" + request.getUrl() + "]にアクセスを検知[" +
                    connAddr.getHostAddress() + "]" ) ;
            }
            // 管理画面表示処理.
            try {
                admin.execution( maachangProxyConfig,manager,refusal,
                    accessManager,conn,seqId,request ) ;
            } catch( Exception e ) {
                LOG.error( "admin-error",e ) ;
                // エラーメッセージを表示する.
                sendErrorMessage( e,conn,host+url,mobileInfo ) ;
            }
            return true ;
        }
        return false ;
    }
    
    /**
     * エラーページ経由の外部ファイルを要求するURLの場合.
     */
    private boolean errorPageToIncludeData( RequestInfo request,ConnectionInfo conn,MobileInfo mobileInfo,String host,String url )
        throws Exception {
        if( request.getUrl().startsWith( ErrorDef.ERROR_URL_HEADER ) ) {
            try {
                String errImgUrl = AnalysisUtil.cutGetParam( request.getUrl() ) ;
                errImgUrl = "/error" + errImgUrl.substring( ErrorDef.ERROR_URL_HEADER.length() ) ;
                errImgUrl = StringUtil.changeString( errImgUrl,"..","" ) ;
                errImgUrl = "." + errImgUrl ;
                byte[] bin = FileUtil.getFile( errImgUrl ) ;
                ResponseInfo response = new ResponseInfo() ;
                response.setBody( bin ) ;
                response.setState( "200" ) ;
                response.setVersion( "1.1" ) ;
                response.addHeader( "Content-Type",ErrorDef.getMimeType( request.getUrl() ) ) ;
                if( response.getBody() != null ) {
                    ConvertMobile.convert( response,mobileInfo ) ;
                }
                settingResponse( response,conn,request ) ;
            } catch( Exception e ) {
                e = new StateException( host,ErrorDef.HTTP11_404,"指定画像は存在しません:" + request.getUrl() ) ;
                // エラーメッセージを表示する.
                sendErrorMessage( e,conn,host+url,mobileInfo ) ;
            }
            return true ;
        }
        return false ;
    }
    
    /**
     * 絵文字イメージキャッシュを要求している場合.
     */
    private boolean emojiImageData( RequestInfo request,ConnectionInfo conn,MobileInfo mobileInfo,String host,String url )
        throws Exception {
        // 絵文字キャッシュを要求している.
        if( request.getUrl().startsWith( EmojiImageCache.URL_HEAD ) ) {
            try {
                String emojiImgUrl = AnalysisUtil.cutGetParam( request.getUrl() ) ;
                emojiImgUrl = emojiImgUrl.substring( EmojiImageCache.URL_HEAD.length() ) ;
                if( emojiImage( emojiImgUrl ) == false ) {
                    throw new Exception() ;
                }
                byte[] bin = EmojiImageCache.getInstance().getImage( emojiImgUrl ) ;
                ResponseInfo response = new ResponseInfo() ;
                response.setBody( bin ) ;
                response.setState( "200" ) ;
                response.setVersion( "1.1" ) ;
                response.addHeader( "Content-Type","image/gif" ) ;
                settingResponse( response,conn,request ) ;
            } catch( Exception e ) {
                e = new StateException( host,ErrorDef.HTTP11_404,"指定絵文字は存在しません:" + request.getUrl() ) ;
                // エラーメッセージを表示する.
                sendErrorMessage( e,conn,host+url,mobileInfo ) ;
            }
            return true ;
        }
        return false ;
    }
    
    /**
     * 指定拒否ユーザエージェントの場合.
     */
    private boolean noAccessClient( ProxyInfo info,MobileData mobileData ) {
        if( info.isAccessMobileFlag() == false && mobileData != null ) {
            return true ;
        }
        if( info.isAccessPcFlag() == false && mobileData == null ) {
            return true ;
        }
        return false ;
    }
    
    /**
     * リクエストホスト名を接続先のホスト名に変換する.
     */
    private void changeConnectionHostName( RequestInfo request,ProxyInfo info )
        throws Exception {
        if( info.getPort() == 80 ) {
            request.putHeader( "Host",info.getDestUrl() ) ;
        }
        else {
            request.putHeader( "Host",new StringBuilder().
                append( info.getDestUrl() ).append( ":" ).
                append( info.getPort() ).toString() ) ;
        }
    }
    
    /**
     * 接続モードに対する終了処理を行う.
     */
    private void exitServerConnector( RequestInfo request,HttpPoolingConnector pool,HttpConnector connector )
        throws Exception {
        if( connector.isLocal() == true || "close".equals( request.getHeader( "Connection",0 ) ) ) {
            if( connector != null ) {
                connector.destroy() ;
            }
        }
        else {
            pool.releaseConnector( connector ) ;
        }
    }
    
    /**
     * Httpレスポンスのヘッダ情報を生成.
     */
    private static final void settingResponse( ResponseInfo res,ConnectionInfo conn,RequestInfo req )
        throws Exception {
        // HTTPコネクションモードを取得.
        boolean closeFlag = true ;
        String connectionMode = req.getHeader( "Connection",0 ) ;
        if( connectionMode != null && ( connectionMode = connectionMode.trim() ).length() > 0 ) {
            if( "keep-alive".equals( connectionMode ) ) {
                closeFlag = false ;
            }
        }
        // レスポンスヘッダを修正.
        res.removeHeader( "Keep-Alive" ) ;
        res.removeHeader( "Connection" ) ;
        res.removeHeader( "Transfer-Encoding" ) ;
        res.removeHeader( "Content-Length" ) ;
        // HttpコネクションがCloseの場合.
        if( closeFlag == true ) {
            res.putHeader( "Connection","close" ) ;
            conn.setCloseFlag( true ) ;
        }
        // HttpコネクションがKeep-Aliveの場合.
        else {
            res.putHeader( "Keep-Alive",new StringBuilder().
                append( "timeout=" ).append( ( conn.getTimeout() /1000L ) ).
                toString() ) ;
            res.addHeader( "Keep-Alive",new StringBuilder().append( "max=" ).
                append( conn.getCount() ).toString() ) ;
            res.putHeader( "Connection","keep-alive" ) ;
        }
        // Bodyが存在しない場合.
        if( res.getBody() == null ) {
            res.putHeader( "Content-Length","0" ) ;
        }
        // Bodyが存在する場合.
        else {
            res.putHeader( "Content-Length",String.valueOf( res.getBody().length ) ) ;
        }
        // サーバ名にProxy内容を付加.
        if( res.getHeader( "Server",0 ) == null ) {
            res.putHeader( "Server",new StringBuilder().
                append( VersionDef.getServerName() ).toString() ) ;
        }
        else {
            res.putHeader( "Server",new StringBuilder().
                append( res.getHeader( "Server",0 ) ).append( "@(" ).
                append( VersionDef.getServerName() ).append( ")" ).toString() ) ;
        }
        // キャッシュオフに設定.
        res.putHeader( "Expires",HttpdTimestamp.getTimestamp( 0L ) );
        res.putHeader( "Pragma","no-cache" ) ;
        res.putHeader( "Last-Modified",HttpdTimestamp.getNowTimestamp() ) ;
        res.putHeader( "Cache-Control","private" ) ;
        res.addHeader( "Cache-Control","no-cache" ) ;
        res.addHeader( "Cache-Control","no-store" ) ;
        res.addHeader( "Cache-Control","max-age=0" ) ;
        // 接続元のクライアント(HTTP)に送信.
        try {
            OutputStream outputStream = conn.getOutputStream() ;
            outputStream.write( res.responseByBinary() ) ;
        } catch( Exception e ) {
            // クライアント(HTTP)コネクション系の例外は、無視する.
            try {
                conn.setCloseFlag( true ) ;
            } catch( Exception ee ) {
            }
        }
    }
    
    /**
     * 直接転送処理を行う場合のヘッダ変換.
     */
    private void settingHeader( ResponseInfo res,ConnectionInfo conn,RequestInfo req )
        throws Exception {
        boolean closeFlag = true ;
        String connectionMode = req.getHeader( "Connection",0 ) ;
        if( connectionMode != null && ( connectionMode = connectionMode.trim() ).length() > 0 ) {
            if( "keep-alive".equals( connectionMode ) ) {
                closeFlag = false ;
            }
        }
        // レスポンスヘッダを修正.
        res.removeHeader( "Keep-Alive" ) ;
        res.removeHeader( "Connection" ) ;
        // HttpコネクションがCloseの場合.
        if( closeFlag == true ) {
            res.putHeader( "Connection","close" ) ;
            conn.setCloseFlag( true ) ;
        }
        // HttpコネクションがKeep-Aliveの場合.
        else {
            res.putHeader( "Keep-Alive",new StringBuilder().
                append( "timeout=" ).append( ( conn.getTimeout() /1000L ) ).
                toString() ) ;
            res.addHeader( "Keep-Alive",new StringBuilder().append( "max=" ).
                append( conn.getCount() ).toString() ) ;
            res.putHeader( "Connection","keep-alive" ) ;
        }
        // サーバ名にProxy内容を付加.
        if( res.getHeader( "Server",0 ) == null ) {
            res.putHeader( "Server",new StringBuilder().
                append( VersionDef.getServerName() ).toString() ) ;
        }
        else {
            res.putHeader( "Server",new StringBuilder().
                append( res.getHeader( "Server",0 ) ).append( "@(" ).
                append( VersionDef.getServerName() ).append( ")" ).toString() ) ;
        }
        // キャッシュオフに設定.
        res.putHeader( "Expires",HttpdTimestamp.getTimestamp( 0L ) );
        res.putHeader( "Pragma","no-cache" ) ;
        res.putHeader( "Last-Modified",HttpdTimestamp.getNowTimestamp() ) ;
        res.putHeader( "Cache-Control","private" ) ;
        res.addHeader( "Cache-Control","no-cache" ) ;
        res.addHeader( "Cache-Control","no-store" ) ;
        res.addHeader( "Cache-Control","max-age=0" ) ;
    }
    
    /**
     * エラーメッセージ出力系の処理.
     */
    private static final void sendErrorMessage( Exception e,ConnectionInfo conn,String host,MobileInfo info ) {
        if( conn != null ) {
            String message = null ;
            int state = ErrorDef.HTTP11_500 ;
            if( e instanceof StateException ) {
                StateException es = ( StateException )e ;
                message = es.getErrorMessage() ;
                state = es.getState() ;
                host = es.getHost() ;
                LOG.warn( "host:" + host + " state:" + state + " message:" + message ) ;
            }
            else {
                message = e.getMessage() ;
            }
            try {
                ResponseInfo res = ErrorPageManager.getInstance().getErrorResponse(
                        info,host,state,null,message ) ;
                if( res.getBody() != null ) {
                    ConvertMobile.convert( res,info ) ;
                }
                OutputStream outputStream = conn.getOutputStream() ;
                outputStream.write( res.responseByBinary() ) ;
            } catch( Exception ee ) {
                try {
                    conn.setCloseFlag( true ) ;
                } catch( Exception eee ) {
                }
            }
        }
    }
    
    /** PC用絵文字の要求が不正であるかチェック. */
    private boolean emojiImage( String url ) {
        if( url == null || url.length() <= 0 ) {
            return false ;
        }
        int len = url.length() ;
        for( int i = 0 ; i < len ; i ++ ) {
            char c = url.charAt( i ) ;
            if( c >= '0' && c <= '9' ) {
                continue ;
            }
            else {
                return false ;
            }
        }
        return true ;
    }
    
    /** GET,POSTのBody内容をProxy先のキャラクタセットで変換. */
    private void convertGetPostDataByCharset( RequestInfo request,ProxyInfo info,MobileData mobileData,EmojiConfig emojiConfig )
        throws Exception {
        if( "POST".equals( request.getMethod() ) && request.getBody() != null && request.getBody().length > 0 ) {
            String bstr = convertDataByCharset( request,info,mobileData,emojiConfig,new String( request.getBody(),"ISO-8859-1" ) ) ;
            request.setBody( bstr.getBytes( info.getCharset() ) ) ;
            bstr = null ;
        }
        int p = request.getUrl().indexOf( "?" ) ;
        if( p >= 0 ) {
            String url = request.getUrl() ;
            String query = url.substring( p+1 ) ;
            query = convertDataByCharset( request,info,mobileData,emojiConfig,query ) ;
            request.setUrl( url.substring( 0,p+1 ) + query ) ;
        }
    }
    
    /** 対象文字列をProxy先のキャラクタセットで変換. */
    private String convertDataByCharset( RequestInfo request,ProxyInfo info,MobileData mobileData,EmojiConfig emojiConfig,String data )
        throws Exception {
        String reqCharset = info.getCharset() ;
        // まだ、キャラクタセットが設定されていない場合.
        if( request.getCharset() == null ) {
            if( mobileData.getCarrier().startsWith( "softbank" ) ) {
                reqCharset = emojiConfig.getCharset( emojiConfig.getCarrierName( mobileData ) ) ;
                if( CharsetMask.WIN31J.equals( reqCharset ) ) {
                    reqCharset = "SHIFT_JIS" ;
                }
            }
            else {
                // DoCoMo,auの場合.
                reqCharset = "SHIFT_JIS" ;
            }
            request.setCharset( reqCharset ) ;
        }
        // request.getCharset() != info.getCharset() の場合は、info.getCharset()で再変換.
        else if( isCharset( request.getCharset(),info.getCharset() ) == false ) {
            reqCharset = request.getCharset() ;
        }
        if( isCharset( reqCharset,info.getCharset() ) ) {
            return data ;
        }
        String src = CharsetMask.getCharset( reqCharset ) ;
        String dest = CharsetMask.getCharset( info.getCharset() ) ;
        if( src == null || dest == null ) {
            return data ;
        }
        return AnalysisUtil.convertBodyByCharset( data,src,dest ) ;
    }
    
    /**
     * 指定charsetを比較.
     */
    private boolean isCharset( String src,String dest ) {
        src = CharsetMask.getCharset( src ) ;
        dest = CharsetMask.getCharset( dest ) ;
        if( src == null || dest == null ) {
            return false ;
        }
        return src.equals( dest ) ;
    }
    
    /**
     * サーバ送受信処理.
     */
    private void sendReceive( RequestInfo request,ConnectionInfo conn,
        ProxyInfo info,HttpPoolingConnector pool,HttpConnector connector,
        MobileInfo mobileInfo,InetAddress connectAddress,String host )
        throws Exception {
        // 接続先のサーバにリクエスト情報を送信.
        connector.send( request ) ;
        
        // サーバからヘッダ情報を受信.
        ResponseInfo response = null ;
        for( int responseCont = 0 ;; responseCont ++ ) {
            if( pool.checkConnect( connector ) == false ) {
                throw new ExpireServerConnectException( "コネクションが切断されています" ) ;
            }
            response = connector.receive(null,null) ;
            if( response == null ) {
                if( pool.checkConnect( connector ) == false ) {
                    throw new ExpireServerConnectException( "コネクションが切断されています" ) ;
                }
                Thread.sleep( WAIT_TIME ) ;
                continue ;
            }
            break ;
        }
        
        // 受信エラーの場合
        // (接続先Hostでエラーページ表示困難なエラーを検知した場合の対応).
        if( response.getState() == null ||
            response.getVersion() == null ||
            response.headerSize() == 0 ) {
            throw new StateException( host,ErrorDef.HTTP11_500,"指定Hostはエラーのため切断されました:" + host ) ;
        }
        
        // 携帯電話アクセスがONに設定されている場合.
        // 受信対象が解析対象の場合は、Bodyをすべて受信.
        if( info.isAccessMobileFlag() == true &&
            ReceiveAnalysisDefine.isAnalysis( response.getHeader( "Content-Type",0 ) ) ) {
            response = connector.receive( null,response ) ;
        }
        // 受信対象が解析対象でない場合は、接続先にServer側のBodyをそのまま送信する.
        else {
            OutputStream outputStream = null ;
            try {
                // Bodyを直接送信.
                settingHeader( response,conn,request ) ;
                outputStream = new BufferedOutputStream( conn.getOutputStream() ) ;
                outputStream.write( response.responseByBinary( false ) ) ;
                connector.receive( outputStream,response ) ;
                outputStream.flush() ;
                // サーバ接続を終了.
                exitServerConnector( request,pool,connector ) ;
                connector = null ;
            } catch( Exception e ) {
                try {
                    conn.setCloseFlag( true ) ;
                } catch( Exception eee ) {
                }
                if( connector != null ) {
                    connector.destroy() ;
                }
            }
            return ;
        }
        // サーバ接続を終了.
        exitServerConnector( request,pool,connector ) ;
        connector = null ;
        
        // 携帯用変換処理.
        if( response.getBody() != null ) {
            ConvertMobile.convert( response,mobileInfo ) ;
        }
        // アクセス追加.
        accessManager.add( host,connectAddress.getHostAddress(),request,response ) ;
        // エラーページである場合.
        ErrorPageManager.getInstance().proxyErrorResponse( response,request,mobileInfo ) ;
        // レスポンスを出力.
        settingResponse( response,conn,request ) ;
    }
}

