package org.maachang.comet.httpd.engine.auth ;

import java.util.HashMap;
import java.util.HashSet;

import org.maachang.comet.httpd.HttpdHeaders;
import org.maachang.comet.httpd.HttpdRequest;
import org.maachang.comet.httpd.HttpdVersionDef;
import org.maachang.comet.httpd.engine.HttpdDef;
import org.maachang.util.Digest;
import org.maachang.util.RandomUtil;

/**
 * HTTPD-認証:Digest.
 *
 * @version 2007/08/19
 * @author  masahito suzuki
 * @since   MaachangComet 1.00
 */
public class HttpdAuthDigest {
    
    /**
     * 認証タイプ.
     */
    public static final String AUTH_TYPE_BY_DIGEST = "digest" ;
    
    /**
     * 認証用Digest.
     */
    private static final String DIGEST = "MD5" ;
    
    /**
     * ランダム生成値.
     */
    public static final int MAX_RANDOM = 0x0fffffff ;
    
    /**
     * コンストラクタ.
     */
    private HttpdAuthDigest() {
        
    }
    
    /**
     * 送信用認証情報を生成.
     * <BR><BR>
     * 送信用認証情報を生成します.
     * <BR>
     * @param element 対象の認証要素を設定します.
     * @return String 送信用認証内容が返されます.
     */
    public static final String getSendAuth( HttpdAuthElement element ) {
        String digest = getDigestByMd5() ;
        if( digest == null ) {
            return null ;
        }
        StringBuilder buf = new StringBuilder() ;
            buf.append( HttpdDef.AUTH_TYPE_DIGEST ).
            append( " realm=" ).
            append( "\"" ).
            append( element.getRealm() ).
            append( "\"" ).
            append( ", " ).
            append( "nonce=" ).
            append( "\"" ).
            append( digest ).
            append( "\"" ).
            append( ", " ).
            append( "algorithm=" ).
            append( HttpdDef.AUTH_DIGEST_MD5 ).
            append( ", " ).
            append( "qop=" ).
            append( "\"" ).
            append( HttpdDef.AUTH_DIGEST_OPTION ).
            append( "\"" ) ;
        return buf.toString() ;
    }
    
    /**
     * 受信内容を元に認証処理.
     * <BR><BR>
     * 受信内容を元に認証処理を行います.
     * <BR>
     * @param request 対象のリクエストを設定します.
     * @param element 対象の認証要素を設定します.
     * @param users 対象のユーザ管理内容を設定します.
     * @param headerParams 対象のHTTPヘッダパラメータを設定します.
     * @return boolean [true]が返された場合、認証成功です.
     */
    public static final boolean isAuth( HttpdRequest request,HttpdAuthElement element,HttpdAuthUsers users,HttpdHeaders headerParams ) {
        HashMap<String,String> params = null ;
        if( ( params = headerParams.getHeaders( HttpdDef.VALUE_AUTH ) ) != null ) {
            try {
                if( params.get( HttpdDef.AUTH_TYPE_DIGEST ) == null ) {
                    return false ;
                }
                String username = params.get( "username" ) ;
                String realm = params.get( "realm" ) ; ;
                String qop = params.get( "qop" ) ; ;
                String algorithm = params.get( "algorithm" ) ; ;
                String url = params.get( "uri" ) ; ;
                String nonce = params.get( "nonce" ) ; ;
                String nc = params.get( "nc" ) ; ;
                String cnonce = params.get( "cnonce" ) ; ;
                String response = params.get( "response" ) ; ;
                // パラメータチェック.
                HashSet userset = element.getUsers() ;
                if( username == null || realm == null || qop == null ||
                    algorithm == null || url == null || nonce == null ||
                    nc == null || cnonce == null || response == null ) {
                    return false ;
                }
                // MD5以外の場合.
                else if( algorithm.equals( HttpdDef.AUTH_DIGEST_MD5 ) == false ){
                    return false ;
                }
                // ユーザが存在しない場合.
                else if( users == null || userset == null ||
                    users.isUser( username ) == false ) {
                    return false ;
                }
                // ユーザマスク内に存在しない.
                else if( userset.size() > 0 && userset.contains( username ) == false ) {
                    return false ;
                }
                userset = null ;
                // パスワード取得.
                String[] passwd = users.getPassword( username ) ;
                if( passwd != null ) {
                    int clen = passwd.length ;
                    for( int i = 0 ; i < clen ; i ++ ) {
                        if( passwd[ i ] == null || passwd[ i ].length() <= 0 ) {
                            passwd[ i ] = "" ;
                        }
                        StringBuilder buf = new StringBuilder() ;
                        // Key1を生成.
                        buf.append( username ) ;
                        buf.append( ":" ).
                            append( realm ).
                            append( ":" ).
                            append( passwd[ i ] ) ;
                        byte[] bin = buf.toString().getBytes( HttpdDef.DEF_CHARSET ) ;
                        String key1 = Digest.convert( DIGEST,bin,0,bin.length ) ;
                        // Key2を生成.
                        buf = new StringBuilder() ;
                        buf.append( request.getMethod() ).
                            append( ":" ).
                            append( url ) ;
                        bin = buf.toString().getBytes( HttpdDef.DEF_CHARSET ) ;
                        String key2 = Digest.convert( DIGEST,bin,0,bin.length ) ;
                        bin = null ;
                        // responseチェック用.
                        buf = new StringBuilder() ;
                        buf.append( key1 ).
                            append( ":" ).
                            append( nonce ).
                            append( ":" ).
                            append( nc ).
                            append( ":" ).
                            append( cnonce ).
                            append( ":" ).
                            append( qop ).
                            append( ":" ).
                            append( key2 ) ;
                        bin = buf.toString().getBytes( HttpdDef.DEF_CHARSET ) ;
                        String check = Digest.convert( DIGEST,bin,0,bin.length ) ;
                        buf = null ;
                        key1 = null ;
                        key2 = null ;
                        // responseと一致するかチェック.
                        if( response.equals( check ) == true ){
                            // 認証ユーザと認証パスワードをリクエストにセット.
                            HttpdAuthManager.setSuccessAuth( request,username,passwd[ i ] ) ;
                            return true ;
                        }
                    }
                }
            } catch( Exception e ) {
                return false ;
            }
        }
        return false ;
    }
    
    /**
     * MD5を生成.
     */
    private static final String getDigestByMd5() {
        try {
            StringBuilder buf = new StringBuilder() ;
            buf.append( "@" ).
                append( HttpdVersionDef.getName() ).
                append( "$" ).
                append( HttpdVersionDef.getVersion() ).
                append( "$" ).
                append( HttpdVersionDef.getDate() ).
                append( "t" ).
                append( System.currentTimeMillis() ).
                append( "r" ).
                append( RandomUtil.random( MAX_RANDOM ) ) ;
            byte[] bin = buf.toString().getBytes( HttpdDef.DEF_CHARSET ) ;
            return Digest.convert( DIGEST,bin,0,bin.length ) ;
        } catch( Exception e ) {
        }
        return null ;
    }
}

