package org.maachang.comet.conf ;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.comet.MaachangDef;
import org.maachang.conf.Config;
import org.maachang.conf.ReadIni;
import org.maachang.util.FileUtil;
import org.maachang.util.NetMask;

/**
 * HTTPDアクセス制御処理.
 * 
 * @version 2008/10/23
 * @author masahito suzuki
 * @since MaachangComet 1.29
 */
public class HttpdAccessConfig {
    
    /**
     * 読み込みファイル名.
     */
    private static final String READ_FILE = MaachangDef.DIRECTORY_CONFIG+"access.conf" ;
    
    /**
     * キャラクタセット.
     */
    private static final String CHARSET = "UTF8" ;
    
    /**
     * チェック間隔.
     */
    private static final long CHECK_TIME = 5000L ;
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( HttpdAccessConfig.class ) ;
    
    /**
     * 対象ファイル.
     */
    private String fileName = null ;
    
    /**
     * 最終更新日.
     */
    private long lastUpdate = -1L ;
    
    /**
     * コンフィグファイル確認最終時間.
     */
    private long lastCheckTime = -1L ;
    
    /**
     * アクセス条件.
     */
    private UrlMaskList masks = null ;
    
    /**
     * コンストラクタ.
     */
    public HttpdAccessConfig() {
        
    }
    
    /**
     * オープン.
     * <BR><BR>
     * オープンします.
     * <BR>
     * @exception Exception 例外.
     */
    public synchronized void open() throws Exception {
        this.open( READ_FILE ) ;
    }
    
    /**
     * オープン.
     * <BR><BR>
     * オープンします.
     * <BR>
     * @param name 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public synchronized void open( String name ) throws Exception {
        this.fileName = FileUtil.getFullPath( name ) ;
        try {
            this.reload() ;
        } catch( Exception e ) {
            this.close() ;
            throw e ;
        } finally {
        }
    }
    
    /**
     * クローズ.
     * <BR><BR>
     * クローズ処理.
     */
    public synchronized void close() {
        fileName = null ;
        lastUpdate = -1L ;
        lastCheckTime = -1L ;
        masks = null ;
    }
    
    /**
     * オブジェクト有効チェック.
     * @return boolean [true]の場合、有効です.
     */
    public synchronized boolean isUse() {
        return ( fileName != null ) ;
    }
    
    /**
     * 接続判別.
     * @param url 対象のURLを設定します.
     * @param address 対象のIPアドレスを設定します.
     * @return boolean [true]の場合、利用できます.
     */
    public synchronized boolean isMask( String url,String address ) {
        if( lastCheckTime + CHECK_TIME <= System.currentTimeMillis() ) {
            long tm = FileUtil.getLastTime( fileName ) ;
            if( lastUpdate != tm ) {
                try {
                    reload() ;
                } catch( Exception e ) {
                    LOG.error( "アクセス制御チェック中にエラー",e ) ;
                    return true ;
                }
            }
            lastCheckTime = System.currentTimeMillis() ;
        }
        if( masks != null ) {
            return masks.isMask( url,address ) ;
        }
        return true ;
    }
    
    /** 再読み込み. */
    private final void reload()
        throws Exception {
        BufferedReader buf = null ;
        if( FileUtil.isFileExists( fileName ) == true ) {
            try {
                buf = new BufferedReader(
                    new InputStreamReader(
                        new FileInputStream( fileName ),CHARSET ) ) ;
                Config conf = new Config() ;
                ReadIni.analisys( conf,buf ) ;
                buf.close() ;
                buf = null ;
                long last = FileUtil.getLastTime( fileName ) ;
                // 読み込みデータを設定.
                if( createMasks( conf ) ) {
                    this.lastUpdate = last ;
                }
            } catch( Exception e ) {
                throw e ;
            } finally {
                if( buf != null ) {
                    try {
                        buf.close() ;
                    } catch( Exception e ) {
                    }
                }
            }
        }
    }
    
    /** マスク情報生成. **/
    private final boolean createMasks( Config conf ) {
        boolean ret = true ;
        int i = 0 ;
        try {
            UrlMaskList mks= null ;
            int len = conf.size( "access","url" ) ;
            {
                int len2 = conf.size( "access","mask" ) ;
                if( len >= len2 ) {
                    len = len2 ;
                }
            }
            mks = new UrlMaskList() ;
            for( i = 0 ; i < len ; ) {
                UrlMask mask = new UrlMask( conf.get( "access","url",i ),
                    conf.get( "access","mask",i ) ) ;
                mks.add( mask ) ;
                i ++ ;
            }
            this.masks = mks ;
        } catch( Exception e ) {
            LOG.error( "access.confエラー("+(i+1)+")",e ) ;
            ret = false ;
        }
        return ret ;
    }
    
}

/**
 * URLマスクリスト.
 */
class UrlMaskList {
    private static final int ASCII = 128 ;
    private Object[] topList = null ;
    private int length = 0 ;
    
    public UrlMaskList() {
        this.clear() ;
    }
    
    protected void finalize() throws Exception {
        topList = null ;
    }
    
    public void clear() {
        topList = new Object[ ASCII ] ;
        length = 0 ;
    }
    
    public void add( UrlMask mask ) {
        String url = null ;
        if( mask == null || ( url = mask.getUrl() ) == null || url.length() <= 1 ) {
            return ;
        }
        int no = ( int )( url.charAt( 1 ) & 0x0000007f ) ;
        UrlMask[] list = null ;
        if( topList[ no ] == null ) {
            list = new UrlMask[ 1 ] ;
            topList[ no ] = list ;
            list[ 0 ] = mask ;
            length ++ ;
        }
        else {
            int len = ( ( UrlMask[] )topList[ no ] ).length ;
            list = new UrlMask[ len+1 ] ;
            System.arraycopy( topList[ no ],0,list,0,len ) ;
            topList[ no ] = list ;
            list[ len ] = mask ;
            length ++ ;
        }
    }
    
    public boolean isMask( String url,String address ) {
        if( url == null || url.length() <= 1 ) {
            return true ;
        }
        if( url.startsWith( "/" ) == false || address == null || address.length() <= 0 ) {
            return false ;
        }
        int no = ( int )( url.charAt( 1 ) & 0x0000007f ) ;
        UrlMask[] list = ( UrlMask[] )topList[ no ] ;
        if( list == null ) {
            return true ;
        }
        int len = list.length ;
        boolean ret = true ;
        for( int i = 0 ; i < len ; i ++ ) {
            UrlMask mk = list[ i ] ;
            if( mk.isUrl( url ) ) {
                if( mk.isMask( address ) ) {
                    return true ;
                }
                ret = false ;
            }
        }
        return ret ;
    }
    
    public int size() {
        return length ;
    }
}

/**
 * URLマスク情報.
 */
class UrlMask {
    private NetMask mask = null ;
    private String url = null ;
    
    private UrlMask() {
        
    }
    
    public UrlMask( String url,String mask )
        throws Exception {
        if( url == null || ( url = url.trim() ).length() <= 0 ||
            mask == null || ( mask = mask.trim() ).length() <= 0 ||
            mask.indexOf( "/" ) == -1 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( url.startsWith( "/" ) == false ) {
            url = url + "/" ;
        }
        NetMask m = new NetMask( mask ) ;
        this.url = url ;
        this.mask = m ;
    }
    
    public boolean isUrl( String url ) {
        if( url == null || url.length() <= 0 ) {
            return false ;
        }
        return url.startsWith( this.url ) ;
    }
    
    public boolean isMask( String ipAddress ) {
        if( ipAddress == null || ipAddress.length() <= 0 ) {
            return false ;
        }
        return this.mask.isRange( ipAddress ) ;
    }
    
    public String getUrl() {
        return url ;
    }
}

