/**
* personium.io
* Copyright 2014 FUJITSU LIMITED
*
* 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 com.fujitsu.dc.core.http.header;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Rangeリクエストヘッダを処理する. RangeHeaderHandler.parse()でRangeヘッダフィールドの値をパースして利用する。
* 返却値はisValid()で有効なヘッダ指定かチェックして利用する。無効で合った場合ヘッダ指定を無視すべき。
* また有効であった場合もでisSatisfiable()でRangeの開始指定が正常なbyte-range-specが存在しない場合は416レスポンスを返却すべき.
* 処理すべきbyte-range-specが存在した場合、getByteRangeSpecCount()で有効なbyte-range-specの数を取得し、
* getFirstBytePos()、getLastBytePos()、getContentLength()、makeContentRangeHeaderField()でレスポンス生成に必要な情報を得る。
*/
public class RangeHeaderHandler {
// 用語
/**
* bytes-unit.
*/
public static final String BYTES_UNIT = "bytes";
// RFCに定義は無いが、apacheが上限13個までの実装になっていたので合わせた
static final int BYTE_RANGE_SPEC_MAX = 13;
// Rangeヘッダフィールドの文字列
private String rangeHeaderField = "";
// range-byte-specを管理
private List<ByteRangeSpec> byteRangeSpecList = new ArrayList<ByteRangeSpec>();;
// Rangeヘッダの有効無効を管理
private boolean valid = false;
/**
* コンストラクタ.
*/
private RangeHeaderHandler(String rangeHeader) {
this.rangeHeaderField = rangeHeader;
}
/**
* Rangeヘッダの値と対象ファイルのサイズを渡しパースして、本クラスのオブジェクトを生成.
* @param rangeHeader Rangeヘッダの値(ex. bytes=500-600,601-999)
* @param entitySize Range指定対象のファイルサイズ
* @return 本クラスのオブジェクト
*/
public static final RangeHeaderHandler parse(final String rangeHeader, final long entitySize) {
RangeHeaderHandler range = new RangeHeaderHandler(rangeHeader);
// Range ヘッダ指定なければ無視
if (rangeHeader == null) {
return range;
}
// byte-range-set 部分の抽出
String regexByteRangesSpecifier = "^bytes\\s*=\\s*(.+)$";
Pattern pByteRangesSpecifier = Pattern.compile(regexByteRangesSpecifier);
Matcher mByteRangesSpecifier = pByteRangesSpecifier.matcher(rangeHeader);
if (!mByteRangesSpecifier.matches()) {
return range;
}
// byte-range-spec が複数ある可能性があるのでパース
String[] byteRangeSpecArray = mByteRangesSpecifier.group(1).split(",");
// byte-range-spec が上限を超えている場合はRangeヘッダ無効
if (byteRangeSpecArray.length > BYTE_RANGE_SPEC_MAX) {
return range;
}
// 各byte-range-specのパース処理
List<ByteRangeSpec> byteRangeSpecList = new ArrayList<ByteRangeSpec>();
for (String byteRangeSpec : byteRangeSpecArray) {
ByteRangeSpec brs = ByteRangeSpec.parse(byteRangeSpec, entitySize);
if (brs == null) {
return range;
}
if (!brs.isInEntitySize()) {
continue;
}
byteRangeSpecList.add(brs);
}
range.setValid();
range.setByteRangeSpec(byteRangeSpecList);
return range;
}
/**
* Rangeヘッダフィールドの文字列を返す.
* @return Rangeヘッダフィールド
*/
public String getRangeHeaderField() {
return this.rangeHeaderField;
}
/**
* Rangeヘッダが有効かどうかを返す. 有効なRange指定がなかった場合falseを返す。
* @return true 有効
*/
public boolean isValid() {
return this.valid;
}
private void setValid() {
this.valid = true;
}
/**
* Rangeヘッダで指定されているbyte-range-specの数を返す.
* @return Rangeの数
*/
public int getByteRangeSpecCount() {
return this.byteRangeSpecList.size();
}
/**
* ファイルの範囲内におさまっているbyte-range-specが存在するかチェック. RFC曰くファイルのbyte-range-specが一つでもファイル内に収まっているものがあれば206で返す.
* @return bool
*/
public boolean isSatisfiable() {
if (this.byteRangeSpecList.size() > 0) {
return true;
}
return false;
}
private void setByteRangeSpec(final List<ByteRangeSpec> brs) {
this.byteRangeSpecList = brs;
}
/**
* 有効なByteRangeSpecのリストを返却.
* @return 有効なByteRangeSpecのリスト
*/
public List<ByteRangeSpec> getByteRangeSpecList() {
return this.byteRangeSpecList;
}
}