/*
このファイルの中身は、NTT オープンソースライセンス　バージョン 1.0（「本
契約」という）の適用を受けます。
本契約を遵守しない限り、このファイルを使用してはなりません。
本契約のコピーは、次のＵＲＬから入手できます。
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

本契約に基づき頒布されるソフトウェアは、現状のまま、明示又は黙示のあらゆ
る種類の保証なしで、頒布されます。本契約に基づく権利及び制限を律する特定
の文言については、本契約を参照してください。

「オリジナルコード」は、 NTT Cyber Space Laboratories Code です。 
「オリジナルコード」の「初期開発者」は、日本電信電話株式会社です。  
日本電信電話株式会社により創作された部分は、Copyright (C) 2004 
日本電信電話株式会社 です。
全ての権利が留保されます。 
「コントリビュータ」：_____________________________________ 


The contents of this file are subject to the NTT Opensource License
Version 1.0 (the License); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

Software distributed under the License is distributed on an AS IS
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is NTT Cyber Space Laboratories Code .

The Initial Developer of the Original Code is NIPPON TELEGRAPH AND 
TELEPHONE CORPORATION.
Portions created by the NIPPON TELEGRAPH AND TELEPHONE CORPORATION 
are Copyright (C) 2004 NIPPON TELEGRAPH AND TELEPHONE CORPORATION. 
All Rights Reserved.

Contributor(s) ______________________________________.
*/

//
// CopyRight (C) 2003, NTT All rights reserved.
//
//	アップロードファイル解析クラス
//			変更履歴
//				2004.01.08	新規作成	五木田 泰史


package jp.co.ntt.lms;

import java.io.DataInputStream;
import java.io.FileOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import javax.servlet.ServletInputStream;

/**
 * アップロードファイル解析クラス
 *
 * <PRE>
 *   アップロードファイルを各部品に分解する。
 * </PRE>
 *
 */
public class MultipartParser
{
	private static String mstrBoundaryKey = "boundary=";
	private static int mintSeparatorSize  = 2;
	private Vector mvecEachParts          = new Vector();
	private String mstrBoundary;
	private String mstrEndBoundary;
	private String mstrContentType;
	private int    mintFileSize;


	/**
	 * アップロードファイル部品取得
	 * @return Vector アップロードファイルの部品
	 */
	public Vector getEachParts()
	{
		return mvecEachParts;
	}

	/**
	 * コンストラクタ
	 * @param String コンテントタイプ
	 * @param String コンテント長
	 * @param String アップロードファイル
	 */
	public MultipartParser(String strContentType,
							int intContentLength,
							ServletInputStream objSis,
							String strBasePath)
		throws IOException
	{
		retrieveBoundary(strContentType);
		retrieveParts(intContentLength, objSis, strBasePath);
	}

	/**
	 * バウンダリーキーの設定
	 * @param String コンテントタイプ
	 * @return void
	 */
	private void retrieveBoundary(String strContentType)
	{
		mstrContentType = strContentType.toLowerCase();
		int intPos = strContentType.indexOf(mstrBoundaryKey)
					+ mstrBoundaryKey.length();
		this.mstrBoundary    = "--" + strContentType.substring(intPos);
		this.mstrEndBoundary = "--" + strContentType.substring(intPos) + "--";
	}

	/**
	 * アップロードファイル分割処理
	 * @param int コンテント長
	 * @param ServletInputStream アップロードファイル
	 * @param String アップロードファイル格納基本フォルダ
	 * @return void
	 * @exception IOException 入出力例外エラー
	 */
	private void retrieveParts(int intContentLength,
								ServletInputStream objSis,
								String strBasePath)
		throws IOException
	{
		byte[] buf               = new byte[8 * 1024];
		byte[] content;
		int intRet;
		int intReadbytes         = 0;
		int intParamsLength      = 0;
		int intEachContentLength = 0;
		boolean boolContentFlag  = false;
		String strLine;
		Part objEachPart         = null;
		DataInputStream objDis   =  new DataInputStream(objSis);

		// アップロードファイルを読み込む
		while ((intRet = objSis.readLine(buf, 0, buf.length)) > -1)
		{
			content = new byte[intRet];											// 8KB長作成
			System.arraycopy(buf, 0, content, 0, intRet);						// 読込みデータコピー
			strLine = new String(content, "Windows-31J");
			if (strLine.indexOf(mstrBoundary) >= 0)								// バウンダリー？
			{
				intReadbytes += intRet;											// 読込みバイト数追加
				if (objEachPart != null)											// パーツが既に生成？
				{
					objEachPart.setParamsLength(intParamsLength);				// パラム長設定
					objEachPart.setContentLength(intEachContentLength);			// パーツコンテント長設定
					mvecEachParts.add(objEachPart);								// パーツリストに追加
				}
				objEachPart          = new Part();								// パーツ作成
				boolContentFlag      = false;									// コンテントフラグ初期化
				intParamsLength      = 0;										// パラメータ長初期化
				intEachContentLength = 0;										// コンテント長初期化
			}
			else if (strLine.length() == mintSeparatorSize)						// 文字列が2(改行コード)？
			{
				intReadbytes += intRet;											// 読込みバイト数追加
				boolContentFlag = true;											// コンテントフラグ設定
				if (objEachPart.isBinary())										// バイナリー？
				{
					break;
				}
			}
			else if (!boolContentFlag)											// コンテントフラグ設定済み？
			{
				intReadbytes += intRet;											// 読込みバイト数追加
				intParamsLength += intRet;										// パラメータ長追加
				objEachPart.setParams(strLine);									// パーツにパラメータ設定
			}
			else if (boolContentFlag)											// コンテントフラグ初期値？
			{
				intReadbytes += intRet;											// 読込みバイト数追加
				intEachContentLength += intRet;									// コンテント長追加
				objEachPart.setContent(content);								// パーツにコンテント設定
			}
		}
		// 出力ファイル作成
		FileOutputStream objFos = null;
		DataOutputStream objDos = null;
		String strFileName = "";
		String strLoType = "";
		String strDirPath = "";
		String strFilePath = "";
		String strSessionID = "";
		mvecEachParts.add(objEachPart);
		Hashtable htbParam = new Hashtable();
		try
		{
			for (int i = 0; i < mvecEachParts.size(); i++)
			{
				Part objPart = (Part)mvecEachParts.get(i);
				htbParam     = (Hashtable)objPart.getParams();
				Enumeration objEnum = htbParam.keys();
				while (objEnum.hasMoreElements())
				{
					String strKey = (String)objEnum.nextElement();
					if (strKey.equals("fileName"))
					{
						strFileName   = (String)htbParam.get(strKey);
						if (strFileName != null && !strFileName.trim().equals( "" ) ) {
							int intIndex  = strFileName.lastIndexOf("\\");
							strFileName   = strFileName.substring(intIndex + 1);
						}
						else {
							strFileName = null;
						}
					}
					if (strKey.equals("SessionID"))
					{
						strSessionID = (String)htbParam.get(strKey);
					}
					if (strKey.equals("RealLoID"))
					{
						strSessionID = (String)htbParam.get(strKey);
					}
					if (strKey.equals("LoType"))
					{
						strLoType = (String)htbParam.get(strKey);
					}
				}
			}

			// ファイル名が設定されている場合のみファイルの書き込みを行う
			if ( strFileName != null ) {
			strDirPath = strBasePath + "/" + strLoType + "/" + strSessionID;

				// ディレクトリの存在チェック
				File objFile   = new File(strDirPath);
				if (!objFile.exists())
				{
					objFile.mkdirs();
				}

				strFilePath = strDirPath + "/" + strFileName;

				// ファイルの存在チェック
				objFile = new File( strFilePath );
				if ( objFile.exists() ) {
					// 同名のファイルがすでに存在した場合は消す
					deleteFile( strFilePath );
				}

				objFos = new FileOutputStream(strFilePath, false);
				objDos = new DataOutputStream(objFos);
				try
				{
					//一度にInputStreamから読み込むバイト数
					//bufのサイズが1024000になっていたが、
					//このままbufを使用すると改修後は合計4Mほど使用することになるので、
					//サイズを小さくする。
					final int BUFFER_LENGTH = 256000;
					buf = new byte[BUFFER_LENGTH];
					byte[] beforBuffer = new byte[BUFFER_LENGTH]; //１回前に読み込んだバッファの内容を格納する
					int beforBufferUsedLength = 0; //実際に使用されているバッファ長
					byte[] boundaryCheckBuffer = new byte[BUFFER_LENGTH * 2]; //バウンダリチェックの際に使用する
					int intReadsize = 0;
					// データ部を読み込む
					int intFlag = 0;
					mintFileSize = 0; //ファイルサイズ初期化
					//Streamからデータが読めなくなったらループ終了
					while ( (intReadsize = objDis.read(buf, 0, buf.length)) != -1)
					{
						//先頭が改行＋バウンダリの場合、ファイルパートが０バイトの為、エラールートに
						//曲げるためのロジックを追加。（完結要件追加による対処）
						if (intFlag == 0)
						{
							//フィールドの先頭にバウンダリがあった場合、
							//ファイルは存在しないのでよみこみ処理を中断する
							content = new byte[102400];
							System.arraycopy(buf, 0, content, 0, 102400);
							String strBounds = new String(content);
							if (strBounds.startsWith("\r\n" + mstrBoundary) == true)
							{
								// 作成したファイルがあった場合削除する
								deleteFile(strFilePath);
								mintFileSize = 0;
								break;
							}
						}
						//１回前の読み込み分と今回の読み込み分を結合し、バウンダリの存在有無を確認する
						System.arraycopy(beforBuffer, 0, boundaryCheckBuffer, 0, beforBufferUsedLength);
						System.arraycopy(buf, 0, boundaryCheckBuffer, beforBufferUsedLength, intReadsize);
						byte[] byteEndBoundary = new String("\r\n" + mstrEndBoundary + "\r\n").getBytes();
						boolean bIsBoundary = false; //バウンダリ発見フラグ(発見時にtrue)
						int i;
						for (i = 0; i < (boundaryCheckBuffer.length - byteEndBoundary.length + 1); i++)
						{
							if (boundaryCheckBuffer[i] == byteEndBoundary[0])
							{
								int j;
								for (j = 1; j < byteEndBoundary.length; j++)
								{
									if (boundaryCheckBuffer[i + j] != byteEndBoundary[j])
									{
										//バウンダリではなかった
										break;
									}
								}
								if (j == byteEndBoundary.length) {
									//バウンダリ発見
									bIsBoundary = true;
									break;
								}
							}
						}
						if (bIsBoundary == true)
						{
							//バウンダリがあった場合、
							//前回読み込み分と今回読み分のバウンダリの直前までを出力する
							//そして、ファイル出力処理ループを抜ける
							objDos.write(boundaryCheckBuffer, 0, i);
							mintFileSize += i;

							break;
						}
						//1回前の読み込み分を出力する
						objDos.write(beforBuffer, 0, beforBufferUsedLength);
						mintFileSize += beforBufferUsedLength;
						System.arraycopy(buf, 0, beforBuffer, 0, intReadsize);
						beforBufferUsedLength = intReadsize;
						intFlag++;
					}
					//もしも、終端まで読み込んでループを抜けた場合
					if (intReadsize == -1)
					{
						objDos.write(beforBuffer, 0, beforBufferUsedLength);
						mintFileSize += beforBufferUsedLength;
					}
				}
				catch (Exception e)
				{
					// 作成したファイルがあった場合削除する
					if (objFos != null && objDos != null)
					{
						objFos.close();
						objDos.close();
						deleteFile(strFilePath);
						deleteFile(strDirPath);
					}
					throw new IOException(e.getMessage());
				}
				objFos.close();
				objDos.close();
				if (mintFileSize == 0)
				{
					// ファイルが０バイトのため削除します
					deleteFile(strFilePath);
					deleteFile(strDirPath);
				}
			}
			else {
				//ファイルパスが設定されていない場合で正常終了に終了した
				mintFileSize = -1;
			}
		}
		catch (Exception e)
		{
			// 作成したファイルがあった場合削除する
			if (objFos != null && objDos != null)
			{
				objFos.close();
				objDos.close();
				deleteFile(strFilePath);
				deleteFile(strDirPath);
			}
			throw new IOException(e.getMessage());
		}
		objDis.close();
	}

	/**
	 * ファイル削除処理
	 * @param String ファイルパス
	 */
	private void deleteFile(String strFilePath)
	{
		try
		{
			// ファイルがあった場合削除する
			File objFile = new File(strFilePath);
			if (objFile.exists())
			{
				objFile.delete();
			}
		}
		catch (Exception e)
		{
		}
	}
	/**
	 * ファイルサイズ取得
	 * ファイルアップロードの失敗は0を返し、ファイルパスが設定されていなかった場合は-1を返す
	 * @return int ファイルサイズ
	 */
	public int getFileSize()
	{
		return mintFileSize;
	}
}