/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.hayabusa.filter;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.opengion.fukurou.security.HybsCryptography;
import org.opengion.fukurou.util.Closer;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.hayabusa.common.HybsSystem;

/**
 * URLCheckFilter は、Filter インターフェースを継承した URLチェッククラスです。
 * web.xml で filter 設定することにより、該当のリソースに対して、og:linkタグで、
 * useURLCheck="true"が指定されたリンクURL以外を拒否することができます。
 * また、og:linkタグを経由した場合でも、リンクの有効期限を設定することで、
 * リンクURLの漏洩に対しても、一定時間の経過を持って、アクセスを拒否することができます。
 * また、リンク時にユーザー情報も埋め込んでいますので(デフォルトは、ログインユーザー)、
 * リンクアドレスが他のユーザーに知られた場合でも、アクセスを拒否することができます。
 *
 * フィルターに対してweb.xml でパラメータを設定します。
 *   ・filename :停止時メッセージ表示ファイル名
 *
 * 【WEB-INF/web.xml】
 *     &lt;filter&gt;
 *         &lt;filter-name&gt;URLCheckFilter&lt;/filter-name&gt;
 *         &lt;filter-class&gt;org.opengion.hayabusa.filter.URLCheckFilter&lt;/filter-class&gt;
 *         &lt;init-param&gt;
 *             &lt;param-name&gt;filename&lt;/param-name&gt;
 *             &lt;param-value&gt;jsp/custom/refuseAccess.html&lt;/param-value&gt;
 *         &lt;/init-param&gt;
 *     &lt;/filter&gt;
 *
 *     &lt;filter-mapping&gt;
 *         &lt;filter-name&gt;URLCheckFilter&lt;/filter-name&gt;
 *         &lt;url-pattern&gt;/jsp/*&lt;/url-pattern&gt;
 *     &lt;/filter-mapping&gt;
 *
 * @og.group フィルター処理
 *
 * @version  4.0
 * @author   Hiroki Nakamura
 * @since    JDK5.0,
 */
public final class URLCheckFilter implements Filter {

	private static final HybsCryptography HYBS_CRYPTOGRAPHY = new HybsCryptography(); // 4.3.7.0 (2009/06/01)

	private String	filename  = null;			// アクセス拒否時メッセージ表示ファイル名
//	private int		maxInterval = 3600;			// リンクの有効期限
	private boolean  isDebug	 = false;
	private boolean  isDecode	 = true;		// 5.4.5.0(2012/02/28) URIDecodeするかどうか

	/**
	 * フィルター処理本体のメソッドです。
	 *
	 * @param request ServletRequest
	 * @param response ServletResponse
	 * @param chain FilterChain
	 * @throws ServletException
	 */
	public void doFilter( final ServletRequest request, final ServletResponse response, final FilterChain chain ) throws IOException, ServletException {

		if( !isValidAccess( request ) ) {
			BufferedReader in = null ;
			try {
				response.setContentType( "text/html; charset=UTF-8" );
				PrintWriter out = response.getWriter();
				in = new BufferedReader( new InputStreamReader(
								new FileInputStream( filename ) ,"UTF-8" ) );
				String str ;
				while( (str = in.readLine()) != null ) {
					out.println( str );
				}
				out.flush();
			}
			catch( UnsupportedEncodingException ex ) {
				String errMsg = "指定されたエンコーディングがサポートされていません。[UTF-8]" ;
				throw new RuntimeException( errMsg,ex );
			}
			catch( IOException ex ) {
				String errMsg = "ストリームがオープン出来ませんでした。[" + filename + "]" ;
				throw new RuntimeException( errMsg,ex );
			}
			finally {
				Closer.ioClose( in );
			}
			return;
		}

		chain.doFilter(request, response);
	}

	/**
	 * フィルターの初期処理メソッドです。
	 *
	 * フィルターに対してweb.xml で初期パラメータを設定します。
	 *   ・maxInterval:リンクの有効期限
	 *   ・filename   :停止時メッセージ表示ファイル名
	 *   ・decode     :URLデコードを行ってチェックするか(初期true)
	 * @og.rev 5.4.5.0 (2102/02/28)
	 * @param filterConfig FilterConfig
	 */
	public void init(final FilterConfig filterConfig) {
		ServletContext context = filterConfig.getServletContext();
		String realPath = context.getRealPath( "/" );

//		maxInterval = StringUtil.nval( filterConfig.getInitParameter("maxInterval"), maxInterval );
		filename  = realPath + filterConfig.getInitParameter("filename");
		isDebug = StringUtil.nval( filterConfig.getInitParameter("debug"), false );
		isDecode = StringUtil.nval( filterConfig.getInitParameter("decode"), true ); // 5.4.5.0(2012/02/28)
	}

	/**
	 * フィルターの終了処理メソッドです。
	 *
	 */
	public void destroy() {
		// ここでは処理を行いません。
	}

	/**
	 * フィルターの内部状態をチェックするメソッドです。
	 *
	 * @og.rev 5.4.5.0 (2012/02/28) Decode
	 * @param request ServletRequest
	 * @return boolean （true:許可  false:拒否）
	 */
	private boolean isValidAccess( final ServletRequest request ) {
		String checkKey = request.getParameter( HybsSystem.URL_CHECK_KEY );
		if( checkKey == null || checkKey.length() == 0 ) {
			if( isDebug ) {
				System.out.println( "  check NG [ No Check Key ]" );
			}
			return false;
		}

		boolean rtn = false;
		try {
			checkKey = HYBS_CRYPTOGRAPHY.decrypt( checkKey ).replace( "&amp;", "&" );

			if( isDebug ) {
				System.out.println( "checkKey=" + checkKey );
			}

			String url = checkKey.substring( 0 , checkKey.lastIndexOf( ",time=") );
			long time = Long.parseLong( checkKey.substring( checkKey.lastIndexOf( ",time=") + 6, checkKey.lastIndexOf( ",userid=" ) ) );
			String userid = checkKey.substring( checkKey.lastIndexOf( ",userid=") + 8 );
			// 4.3.8.0 (2009/08/01)
			String[] userArr = StringUtil.csv2Array( userid );
			
			if( isDebug ) {
				System.out.println( " [url]    =" + url );
				System.out.println( " [vtime]  =" + time );
				System.out.println( " [userid] =" + userid );
			}

			String reqStr =  ((HttpServletRequest)request).getRequestURL().toString() + "?" + ((HttpServletRequest)request).getQueryString();
			// 5.4.5.0 (2012/02/28) URLDecodeを行う
			if(isDecode){
				if( isDebug ) {
					System.out.println( "[BeforeURIDecode]="+reqStr );
				}
				reqStr = StringUtil.urlDecode( reqStr );
			}
			reqStr = reqStr.substring( 0, reqStr.lastIndexOf( HybsSystem.URL_CHECK_KEY ) -1 );
			//	String reqStr =  ((HttpServletRequest)request).getRequestURL().toString();
			String reqUser = ((HttpServletRequest)request).getRemoteUser();

			if( isDebug ) {
				System.out.println( " [reqURL] =" + reqStr );
				System.out.println( " [ctime]  =" + System.currentTimeMillis() );
				System.out.println( " [reqUser]=" + reqUser );
			}
			
			if( reqStr.endsWith( url )
//					&& System.currentTimeMillis() - time < maxInterval * 1000
					&& System.currentTimeMillis() - time < 0
//					&& userid.equals( reqUser ) ) {
					&& userArr != null && userArr.length > 0 ) {
				// 4.3.8.0 (2009/08/01)
				for( int i=0; i<userArr.length; i++ ) {
					if( "*".equals( userArr[i] ) || reqUser.equals( userArr[i] ) ) {
						rtn = true;
						if( isDebug ) {
							System.out.println( "  check OK" );
						}
						break;
					}
				}
			}
		}
		catch( RuntimeException ex ) {
			if( isDebug ) {
				String errMsg = "チェックエラー。 "
							+ " checkKey=" + checkKey
							+ " " + ex.getMessage();			// 5.1.8.0 (2010/07/01) errMsg 修正
				System.out.println( errMsg );
				ex.printStackTrace();
			}
			rtn = false;
		}
		return rtn;
	}

	/**
	 * 内部状態を文字列で返します。
	 *
	 * @return String このクラスの文字列表示
	 */
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append( "UrlCheckFilter" );
//		sb.append( "[" ).append( maxInterval ).append( "],");
		sb.append( "[" ).append( filename  ).append( "],");
		return (sb.toString());
	}
}
