/**
* 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.jersey.filter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fujitsu.dc.common.utils.DcCoreUtils;
import com.fujitsu.dc.common.utils.DcCoreUtils.HttpHeaders;
import com.fujitsu.dc.core.DcCoreConfig;
import com.fujitsu.dc.core.DcCoreException;
import com.fujitsu.dc.core.DcReadDeleteModeManager;
import com.fujitsu.dc.core.model.lock.CellLockManager;
import com.sun.jersey.core.header.InBoundHeaders;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
/**
* 本アプリのリクエスト及びレスポンスに対してかけるフィルター.
*/
public final class DcCoreContainerFilter implements ContainerRequestFilter, ContainerResponseFilter {
static Logger log = LoggerFactory.getLogger(DcCoreContainerFilter.class);
// Acceptヘッダーが取り得る値の正規表現
static Pattern acceptHeaderValueRegex = Pattern.compile("\\A\\p{ASCII}*\\z");
@Context
private HttpServletRequest httpServletRequest;
/**
* @param httpServletRequest HttpServletRequest
*/
public void setHttpServletRequest(final HttpServletRequest httpServletRequest) {
this.httpServletRequest = httpServletRequest;
}
/**
* リクエスト全体に対してかけるフィルター.
* @param request フィルタ前リクエスト
* @return フィルタ後リクエスト
*/
@Override
public ContainerRequest filter(ContainerRequest request) {
requestLog(request);
// リクエストの時間を記録する
long requestTime = System.currentTimeMillis();
// リクエストの時間をセッションに保存する
this.httpServletRequest.setAttribute("requestTime", requestTime);
methodOverride(request);
headerOverride(request);
uriOverride(request);
responseOptionsMethod(request);
// リクエストヘッダーの不正値をチェックする
checkRequestHeader(request);
// DcCoreConfig.setUnitRootIfNotSet(this.httpServletRequest);
// PCSの動作モードがReadDeleteOnlyモードの場合は、参照系リクエストのみ許可する
// 許可されていない場合は例外を発生させてExceptionMapperにて処理する
DcReadDeleteModeManager.checkReadDeleteOnlyMode(request.getMethod(), request.getPathSegments());
return request;
}
/**
* レスポンス全体に対してかけるフィルター.
* @param request リクエスト
* @param response フィルタ前レスポンス
* @return フィルタ後レスポンス
*/
@Override
public ContainerResponse filter(final ContainerRequest request, final ContainerResponse response) {
String cellId = (String) httpServletRequest.getAttribute("cellId");
if (cellId != null) {
CellLockManager.decrementReferenceCount(cellId);
}
// 全てのレスポンスに共通するヘッダを追加する
addResponseHeaders(request, response);
// レスポンスログを出力
responseLog(response);
return response;
}
/**
* メソッドオーバーライド処理.
* @param request 加工するリクエスト
*/
private void methodOverride(final ContainerRequest request) {
if (request.getMethod().equalsIgnoreCase(HttpMethod.POST)) {
// メソッドオーバーライド
String method = request.getRequestHeaders().getFirst(DcCoreUtils.HttpHeaders.X_HTTP_METHOD_OVERRIDE);
if (method != null && !method.isEmpty()) {
request.setMethod(method);
}
}
}
/**
* ヘッダオーバーライド処理.
* @param request 加工するリクエスト
*/
private void headerOverride(final ContainerRequest request) {
// ヘッダオーバーライド
List<String> overrideHeaderList = request.getRequestHeaders().get(DcCoreUtils.HttpHeaders.X_OVERRIDE);
if (overrideHeaderList == null) {
return;
}
InBoundHeaders headers = new InBoundHeaders();
MultivaluedMap<String, String> originalHeaders = request.getRequestHeaders();
if (originalHeaders instanceof InBoundHeaders) {
headers = (InBoundHeaders) originalHeaders;
}
InBoundHeaders overrideHeaders = new InBoundHeaders();
for (String overrideHeader : overrideHeaderList) {
int idx = overrideHeader.indexOf(":");
// :がなかったり先頭にある場合は不正ヘッダなので無視
if (idx < 1) {
continue;
}
String key = overrideHeader.substring(0, idx).trim();
String value = overrideHeader.substring(idx + 1).trim();
List<String> vl = overrideHeaders.get(key);
if (vl == null) {
vl = new ArrayList<String>();
}
vl.add(value);
overrideHeaders.put(key, vl);
}
for (Entry<String, List<String>> entry : overrideHeaders.entrySet()) {
headers.put(entry.getKey(), entry.getValue());
}
request.setHeaders(headers);
}
/**
* Uriのオーバーライド処理.
* @param request 加工するリクエスト
*/
private void uriOverride(final ContainerRequest request) {
String xForwardedProto = request.getHeaderValue(DcCoreUtils.HttpHeaders.X_FORWARDED_PROTO);
String xForwardedHost = request.getHeaderValue(DcCoreUtils.HttpHeaders.X_FORWARDED_HOST);
String xForwardedPath = request.getHeaderValue(DcCoreUtils.HttpHeaders.X_FORWARDED_PATH);
UriBuilder bub = request.getBaseUriBuilder();
UriBuilder rub = request.getRequestUriBuilder();
if (xForwardedProto != null) {
bub.scheme(xForwardedProto);
rub.scheme(xForwardedProto);
}
if (xForwardedHost != null) {
bub.host(xForwardedHost);
rub.host(xForwardedHost);
}
if (xForwardedPath != null) {
bub.replacePath("/");
// クエリを含んでいる場合は、クエリを削除してリクエストパスに設定する
if (xForwardedPath.contains("?")) {
xForwardedPath = xForwardedPath.substring(0, xForwardedPath.indexOf("?"));
}
rub.replacePath(xForwardedPath);
}
request.setUris(bub.build(), rub.build());
}
/**
* 全てのレスポンスに共通するレスポンスヘッダーを追加する.
* Access-Control-Allow-Origin, Access-Control-Allow-Headers<br/>
* X-Dc-Version<br/>
* @param request
* @param response
*/
private void addResponseHeaders(final ContainerRequest request, final ContainerResponse response) {
MultivaluedMap<String, Object> mm = response.getHttpHeaders();
String acrh = request.getHeaderValue(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
if (acrh != null) {
mm.putSingle(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, acrh);
} else {
mm.remove(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS);
}
mm.putSingle(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, HttpHeaders.Value.ASTERISK);
// X-Dc-Version
mm.putSingle(HttpHeaders.X_DC_VERSION, DcCoreConfig.getCoreVersion());
}
/**
* リクエストログ出力.
* @param request
* @param response
*/
private void requestLog(final ContainerRequest request) {
StringBuilder sb = new StringBuilder();
sb.append("[" + DcCoreConfig.getCoreVersion() + "] " + "Started. ");
sb.append(request.getMethod());
sb.append(" ");
sb.append(request.getRequestUri().toString());
sb.append(" ");
sb.append(this.httpServletRequest.getRemoteAddr());
log.info(sb.toString());
}
/**
* レスポンスログ出力.
* @param response
*/
private void responseLog(final ContainerResponse response) {
StringBuilder sb = new StringBuilder();
sb.append("[" + DcCoreConfig.getCoreVersion() + "] " + "Completed. ");
sb.append(response.getStatus());
sb.append(" ");
// レスポンスの時間を記録する
long responseTime = System.currentTimeMillis();
// セッションからリクエストの時間を取り出す
long requestTime = (Long) this.httpServletRequest.getAttribute("requestTime");
// レスポンスとリクエストの時間差を出力する
sb.append((responseTime - requestTime) + "ms");
log.info(sb.toString());
}
/**
* 認証なしOPTIONメソッドのレスポンスを返却する.
* @param request フィルタ前リクエスト
*/
private void responseOptionsMethod(ContainerRequest request) {
String authValue = request.getHeaderValue(org.apache.http.HttpHeaders.AUTHORIZATION);
String methodName = request.getMethod();
if (authValue == null && HttpMethod.OPTIONS.equals(methodName)) {
Response res = DcCoreUtils.responseBuilderForOptions(
HttpMethod.GET,
HttpMethod.POST,
HttpMethod.PUT,
HttpMethod.DELETE,
HttpMethod.HEAD,
com.fujitsu.dc.common.utils.DcCoreUtils.HttpMethod.MERGE,
com.fujitsu.dc.common.utils.DcCoreUtils.HttpMethod.MKCOL,
com.fujitsu.dc.common.utils.DcCoreUtils.HttpMethod.MOVE,
com.fujitsu.dc.common.utils.DcCoreUtils.HttpMethod.PROPFIND,
com.fujitsu.dc.common.utils.DcCoreUtils.HttpMethod.PROPPATCH,
com.fujitsu.dc.common.utils.DcCoreUtils.HttpMethod.ACL
).build();
// 例外を発行することでServletへ制御を渡さない
throw new WebApplicationException(res);
}
}
/**
* リクエストヘッダーの値をチェックする.
* 現在は、Acceptヘッダーのみ(US-ASCII文字以外かどうか)をチェックする
* @param request フィルター前リクエスト
*/
private void checkRequestHeader(ContainerRequest request) {
// ヘッダーのキー名に全角文字が含まれる場合は、その文字を含めたキー名となるため、実際にはこの指定は無視される。
// Jersey1.10では、Acceptヘッダーのキー名と値にUS-ASCII文字以外が含まれる場合に異常終了するため以下を対処
// (Acceptを含む他のヘッダーにも同様の処理が行われるが、上記理由により動作上は問題ないと判断)
// -キー名に含まれる場合は、その指定を無効(Accept:*/*)とする(Jerseryで組み込み済み)。
// -値に含まれる場合は、400エラーとする。
InBoundHeaders newHeaders = new InBoundHeaders();
MultivaluedMap<String, String> headers = request.getRequestHeaders();
for (String header : headers.keySet()) {
if (header.contains(org.apache.http.HttpHeaders.ACCEPT)
&& !acceptHeaderValueRegex.matcher(header).matches()) {
continue;
} else {
newHeaders.put(header, request.getRequestHeader(header));
}
}
request.setHeaders(newHeaders);
String acceptValue = request.getHeaderValue(org.apache.http.HttpHeaders.ACCEPT);
if (acceptValue != null && !acceptHeaderValueRegex.matcher(acceptValue).matches()) {
DcCoreException exception = DcCoreException.OData.BAD_REQUEST_HEADER_VALUE.params(
org.apache.http.HttpHeaders.ACCEPT, acceptValue);
throw exception;
}
}
}