All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nablarch.fw.web.upload.MultipartInputStream Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
package nablarch.fw.web.upload;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import javax.servlet.ServletInputStream;

import nablarch.core.log.Logger;
import nablarch.core.log.LoggerManager;
import nablarch.core.util.StringUtil;
import nablarch.fw.results.BadRequest;
import nablarch.fw.results.RequestEntityTooLarge;

/**
 * マルチパート用の入力ストリーム。
* 境界文字列を認識してストリームを読み込む機能を提供する。 * * @author T.Kawasaki */ class MultipartInputStream { /** ログ */ private static final Logger LOG = LoggerManager.get(MultipartInputStream.class); /** ストリーム終端に達したことを表す定数 */ private static final int EOS = -1; /** 入力ストリーム */ private final ServletInputStream in; /** コンテキスト */ private final MultipartContext ctx; /** 読み込み許容最大値 */ private final int limit; /** 現在までに読みこまれたバイト数 */ private int readCount = 0; /** キャッシュしているバイト */ private byte[] cache; /** キャッシュへ転送が終わったバイト数(ストリーム→キャッシュ用)。fill()でカウントアップ。 */ private int count; /** 現在のキャッシュ位置(キャッシュ→バッファ用)。read()でカウントアップ。 */ private int pos; /** 境界文字列が見つかった場合(該当Contentが終わりに達した時) true */ private boolean eof; /** 境界文字列 */ private String boundary; /** * コンストラクタ。 * * @param in  入力ストリーム * @param ctx コンテキスト * @param limit 読み込み許容最大値 */ MultipartInputStream(ServletInputStream in, MultipartContext ctx, int limit) { this.in = in; this.ctx = ctx; this.limit = limit; } /** * ストリームから1行の読み込みを行う。
* ストリームから識別子を取り出すときにのみ使用される。 * * @return 読み込み行(読み込むデータがなかった場合はnull) * @throws IOException 入出力例外 */ String readLine() throws IOException { final int bufferSize = 4 * 1024; final int limitSize = 10 * 1024; ByteArrayOutputStream out = new ByteArrayOutputStream(bufferSize); byte[] buf = new byte[bufferSize]; int readBytes; do { try { readBytes = in.readLine(buf, 0, buf.length); } catch (IOException e) { // クライアントから切断された場合の処理。 LOG.logWarn("incomplete upload data.", e); throw new BadRequest("incomplete uploading", e); } if (readBytes != EOS) { addReadCount(readBytes); out.write(buf, 0, readBytes); } } while (readBytes == buf.length && out.size() <= limitSize); if (out.size() == 0) { return null; } if (out.size() > limitSize) { // Boundary内のヘッダ行のサイズが10KBを超えた場合は、リクエストが改竄されていると判断する。 throw new BadRequest("header record size is too large. header record size = [" + out.size() + ']'); } String line = new String(out.toByteArray(), ctx.getRequestCharacterEncoding()); line = StringUtil.chomp(line, "\n"); line = StringUtil.chomp(line, "\r"); return line; } /** * 境界文字列まで入力ストリームをスキップする。 * * @param boundary 境界文字列 * @throws IOException 入出力例外 */ void skipTillBoundary(String boundary) throws IOException { String line; do { line = readLine(); if (line == null) { throw new BadRequest("no data found."); } } while (!line.startsWith(boundary)); this.boundary = boundary; } /** * pbuf.length読み込み終わった時もしくは、count - pos が2以下( * つまりキャッシュの最後まで読込み完了)時に本メソッドが終了する。 * * @param pbuf バッファ * @return 読み込みバイト数 * @throws IOException 入出力例外 */ int read(byte[] pbuf) throws IOException { // バッファの位置。 int readBytesNum; int rest = countRestLength(); // 読み込みが最初の場合は、下記条件式が成立する。 // ※count == 0 & pos == 0だから。 if (rest <= 0) { fill(); // ストリームからキャッシュにデータを溜め込む処理。 rest = countRestLength(); if (rest <= 0) { // キャッシュには何も読込まれなかった。 return EOS; } } // restはキャッシュ内に残っている転送が済んでいない // データのサイズを表している。 // restが一杯残っている場合は、pbuf.lengthが入る。 int transferredBytesNum = Math.min(pbuf.length, rest); System.arraycopy(cache, pos, pbuf, 0, transferredBytesNum); pos += transferredBytesNum; // バッファへの転送が終わったキャッシュの位置を更新。 readBytesNum = transferredBytesNum; // バッファが溜まるまで、残りを読み込む。 // ※読み込みが途中の場合(キャッシュがバッファに入りきらなかった場合)、 //  下記はスキップされる。 while (readBytesNum < pbuf.length) { fill(); // ストリームからキャッシュにデータを溜め込む。 rest = countRestLength(); if (rest <= 0) { return readBytesNum; // キャッシュには何も読込まれなかった。 } transferredBytesNum = Math.min(pbuf.length - readBytesNum, rest); System.arraycopy(cache, pos, pbuf, readBytesNum, transferredBytesNum); pos += transferredBytesNum; readBytesNum += transferredBytesNum; } return readBytesNum; } /** * パラメータを読みとる。
* * @return パラメータ文字列 * @throws IOException 入出力例外 */ String readParam() throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(512); byte[] buf256 = new byte[256]; int readBytes; while ((readBytes = read(buf256)) != EOS) { out.write(buf256, 0, readBytes); } return out.toString(ctx.getRequestCharacterEncoding()); } /** * 読み込まれるべき残りのバイト数を計算する。 * * @return バイト数 */ private int countRestLength() { // 読み込まれるデータの最後に余分な2バイトのデータが存在する。 // ので差し引いたバイト数が、残りの読込まなければいけないバイト数。 return count - pos - 2; } /** * ストリームからboundary文字列までをキャッシュに格納する。
* もしくは、キャッシュサイズまでストリームのデータを読込む。 * 境界文字列に出会っている場合は、何も行わない。 * * @throws IOException 入出力例外 */ private void fill() throws IOException { // boundaryに出会っていたらeofがtrueになっている。 if (eof) { return; } // ストリームの続きの読み込みを行う場合は、下記条件式が成立する。 // ※各パートのリード前にcount = 0を行なっているため、最初の読込みでは // 成立しない。 // この時(count - pos == 2)が成立しない時は、キャッシュへのアップロードが // 終了していないことを示す。(ブラウザのストップボタン等) if (count > 0) { // 連結部分なので、最後の2バイトは切り捨てるのではなく、 // 次のキャッシュの先頭につけている。 if (count - pos == 2) { System.arraycopy(cache, pos, cache, 0, 2); count = 2; pos = 0; } else { throw new BadRequest("incomplete uploading."); } } int read = 0; int boundaryLength = boundary.length(); int maxRead = cache.length - boundaryLength - 2; // 2は\r\n // ストリームからキャッシュへの転送を行う。 while (count < maxRead) { try { read = in.readLine(cache, count, cache.length - count); } catch (IOException e) { // データ送信中にクライアントから切断(ブラウザの中止)された場合の処理 // 発生したエラーをワーニングレベルでログ出力し、不正なリクエストデータであることを示すBadRequestを送出する。 LOG.logWarn("incomplete upload data.", e); throw new BadRequest("incomplete uploading", e); } // boundary文字列が最後に来ないのに、ストリームが終わった場合は // 意図しないエラーである。 if (read == EOS) { throw new BadRequest("input stream unexpectedly ended before boundary appears."); } addReadCount(read); // 読み込んだバッファがboundary文字列だったらeofを立てる。 if (read >= boundaryLength) { eof = true; for (int i = 0; i < boundaryLength; i++) { if (boundary.charAt(i) != cache[count + i]) { eof = false; // 直前のfor文から抜ける。 break; } } if (eof) { // while文から抜ける。 // キャッシュの最後がboundary文字列である状態。 // count +=が実行されないので、キャッシュ内に // boundary文字列があるが、キャッシュからバッファへは // 転送されない。 break; } } count += read; // 読み込んだcountを増やす。 } } /** * 読み込みバイト数を加算する。 * * @param count 加算するバイト数。 * @throws RequestEntityTooLarge Content-Length制限値を超過した場合 */ private void addReadCount(int count) throws RequestEntityTooLarge { readCount += count; if (readCount > limit) { consume(); throw new RequestEntityTooLarge(); } } /** 何もせずにrequestを消化する。 */ void consume() { try { while (in.skip(1024 * 1024) > 0) { // SUPPRESS CHECKSTYLE 読み飛ばしのため // NOP } } catch (IOException ignored) { // SUPPRESS CHECKSTYLE クライアントから接続が切断された場合などに発生するため // 正常時には本処理は呼び出されないため、クライアントからの切断されIOExceptionが発生した場合でも何もしない。 } } /** * 読み込み用の値を初期化する。
* パートひとつ読み取る毎に初期化する必要がある。 */ void reset() { cache = new byte[64 * 1024]; count = 0; pos = 0; eof = false; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy