View Javadoc

    1   /*
    2    * Copyright 2004-2009 the Seasar Foundation and the Others.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    13   * either express or implied. See the License for the specific language
    14   * governing permissions and limitations under the License.
    15   */
    16  package org.seasar.cubby.filter;
    17
    18  import java.io.IOException;
    19  import java.io.UnsupportedEncodingException;
    20  import java.nio.charset.Charset;
    21
    22  import javax.servlet.Filter;
    23  import javax.servlet.FilterChain;
    24  import javax.servlet.FilterConfig;
    25  import javax.servlet.ServletException;
    26  import javax.servlet.ServletRequest;
    27  import javax.servlet.ServletResponse;
    28  import javax.servlet.http.HttpServletRequest;
    29  import javax.servlet.http.HttpServletRequestWrapper;
    30
    31  /**
    32   * 要求のエンコーディングを設定するためのフィルタです。
    33   * <p>
    34   * 初期化パラメータ {@value #ENCODING}、{@value #FORCE_ENCODING} で要求の文字エンコーディングを指定します。
    35   * </p>
    36   * <p>
    37   * 初期化パラメータ {@value #URI_ENCODING}、{@value #URI_BYTES_ENCODING} で
    38   * {@link HttpServletRequest#getServletPath()}、
    39   * {@link HttpServletRequest#getPathInfo()} で取得できるパスのエンコーディングを指定します。
    40   * </p>
    41   * <p>
    42   * <table>
    43   * <thead>
    44   * <tr>
    45   * <th>param-name</th>
    46   * <th>param-value</th>
    47   * </tr>
    48   * <tbody>
    49   * <tr>
    50   * <td>{@value #ENCODING}</td>
    51   * <td>要求のエンコーディングを指定します。要求のエンコーディングが <code>null</code> か、
    52   * {@value #FORCE_ENCODING} に <code>true</code> が指定された場合はこのエンコーディングが要求に設定されます。</td>
    53   * </tr>
    54   * <tr>
    55   * <td>{@value #FORCE_ENCODING}</td>
    56   * <td><code>true</code> を指定した場合は、要求にエンコーディングが設定されていても {@value #ENCODING}
    57   * で上書きします。
    58   * </tr>
    59   * <tr>
    60   * <td>{@value #URI_ENCODING}</td>
    61   * <td>URI のエンコーディングを指定します。</td>
    62   * </tr>
    63   * <tr>
    64   * <td>{@value #URI_BYTES_ENCODING}</td>
    65   * <td>URI をバイト配列として取得する際のエンコーディングを指定します。</td>
    66   * </tr>
    67   * </tbody>
    68   * </table>
    69   * <caption>初期化パラメータ</caption>
    70   * </p>
    71   *
    72   * @author baba
    73   */
    74  public class EncodingFilter implements Filter {
    75
    76  	private static final String ALREADY_FILTERED_ATTRIBUTE_NAME = EncodingFilter.class
    77  			.getName()
    78  			+ ".FILTERED";
    79
    80  	/** エンコーディングのキー。 */
    81  	public static final String ENCODING = "encoding";
    82
    83  	/** 強制エンコーディング設定のキー。 */
    84  	public static final String FORCE_ENCODING = "forceEncoding";
    85
    86  	/** URI エンコーディングのキー。 */
    87  	public static final String URI_ENCODING = "URIEncoding";
    88
    89  	/** URI バイト列のエンコーディングのキー。 */
    90  	public static final String URI_BYTES_ENCODING = "URIBytesEncoding";
    91
    92  	/** URI バイト列のエンコーディングのデフォルト値。 */
    93  	public static final String DEFAULT_URI_BYTE_ENCODING = "ISO-8859-1";
    94
    95  	/** エンコーディング。 */
    96  	private String encoding;
    97
    98  	/** 強制エンコーディング設定。 */
    99  	private boolean forceEncoding = false;
    100
    101 	/** URI エンコーディング。 */
    102 	private String uriEncoding;
    103
    104 	/** URI バイト列のエンコーディング。 */
    105 	private String uriBytesEncoding;
    106
    107 	/**
    108 	 * {@inheritDoc}
    109 	 */
    110 	public void init(final FilterConfig config) throws ServletException {
    111 		encoding = config.getInitParameter(ENCODING);
    112 		try {
    113 			validateEncoding(encoding);
    114 		} catch (final UnsupportedEncodingException e) {
    115 			throw new ServletException(e);
    116 		}
    117
    118 		final String forceEncodingString = config
    119 				.getInitParameter(FORCE_ENCODING);
    120 		if (forceEncodingString != null) {
    121 			forceEncoding = Boolean.parseBoolean(forceEncodingString);
    122 		}
    123
    124 		uriEncoding = config.getInitParameter(URI_ENCODING);
    125 		try {
    126 			validateEncoding(uriEncoding);
    127 		} catch (final UnsupportedEncodingException e) {
    128 			throw new ServletException(e);
    129 		}
    130
    131 		uriBytesEncoding = config.getInitParameter(URI_BYTES_ENCODING);
    132 		if (uriBytesEncoding == null) {
    133 			uriBytesEncoding = DEFAULT_URI_BYTE_ENCODING;
    134 		}
    135 		try {
    136 			validateEncoding(uriBytesEncoding);
    137 		} catch (final UnsupportedEncodingException e) {
    138 			throw new ServletException(e);
    139 		}
    140 	}
    141
    142 	/**
    143 	 * 指定されたエンコーディングがサポートされているか検査します。
    144 	 *
    145 	 * @param encoding
    146 	 *            エンコーディング
    147 	 * @throws UnsupportedEncodingException
    148 	 *             指定されたエンコーディングがサポートされていない場合
    149 	 */
    150 	private void validateEncoding(final String encoding)
    151 			throws UnsupportedEncodingException {
    152 		if (encoding != null && !Charset.isSupported(encoding)) {
    153 			throw new UnsupportedEncodingException(encoding);
    154 		}
    155 	}
    156
    157 	/**
    158 	 * {@inheritDoc}
    159 	 */
    160 	public void destroy() {
    161 	}
    162
    163 	/**
    164 	 * {@inheritDoc}
    165 	 */
    166 	public void doFilter(final ServletRequest request,
    167 			final ServletResponse response, final FilterChain chain)
    168 			throws IOException, ServletException {
    169 		if (request.getAttribute(ALREADY_FILTERED_ATTRIBUTE_NAME) == null) {
    170 			request.setAttribute(ALREADY_FILTERED_ATTRIBUTE_NAME, Boolean.TRUE);
    171 			if (request.getCharacterEncoding() == null || forceEncoding) {
    172 				request.setCharacterEncoding(encoding);
    173 			}
    174 			if (uriEncoding == null) {
    175 				chain.doFilter(request, response);
    176 			} else {
    177 				final ServletRequest wrapper = new EncodingHttpServletRequestWrapper(
    178 						(HttpServletRequest) request, uriEncoding,
    179 						uriBytesEncoding);
    180 				chain.doFilter(wrapper, response);
    181 			}
    182 			request.removeAttribute(ALREADY_FILTERED_ATTRIBUTE_NAME);
    183 		} else {
    184 			chain.doFilter(request, response);
    185 		}
    186 	}
    187
    188 	/**
    189 	 * 適切なエンコーディングで処理するための {@link HttpServletRequestWrapper} です。
    190 	 *
    191 	 * @author baba
    192 	 */
    193 	private static class EncodingHttpServletRequestWrapper extends
    194 			HttpServletRequestWrapper {
    195
    196 		/** URI エンコーディング。 */
    197 		private final String uriEncoding;
    198
    199 		/** URI バイト列のエンコーディング。 */
    200 		private final String uriBytesEncoding;
    201
    202 		/**
    203 		 * 指定された要求をラップします。
    204 		 *
    205 		 * @param request
    206 		 *            要求
    207 		 * @param uriEncoding
    208 		 *            URI エンコーディング
    209 		 * @param uriBytesEncoding
    210 		 *            URI バイト列のエンコーディング。
    211 		 * @throws IOException
    212 		 *             スーパクラスのコンストラクタで例外が発生した場合
    213 		 */
    214 		public EncodingHttpServletRequestWrapper(
    215 				final HttpServletRequest request, final String uriEncoding,
    216 				final String uriBytesEncoding) throws IOException {
    217 			super(request);
    218 			this.uriEncoding = uriEncoding;
    219 			this.uriBytesEncoding = uriBytesEncoding;
    220 		}
    221
    222 		@Override
    223 		public String getServletPath() {
    224 			return rebuild(super.getServletPath(), uriEncoding,
    225 					uriBytesEncoding);
    226 		}
    227
    228 		@Override
    229 		public String getPathInfo() {
    230 			return rebuild(super.getPathInfo(), uriEncoding, uriBytesEncoding);
    231 		}
    232
    233 		/**
    234 		 * 指定された文字列を一度 <code>bytesEncoding</code> でバイト配列に戻し、
    235 		 * <code>encoding</code> で文字列を再構築します。
    236 		 *
    237 		 * @param str
    238 		 *            文字列
    239 		 * @param encoding
    240 		 *            エンコーディング
    241 		 * @param bytesEncoding
    242 		 *            バイト配列に戻す時のエンコーディング
    243 		 * @return 再構築された文字列
    244 		 */
    245 		private String rebuild(final String str, final String encoding,
    246 				final String bytesEncoding) {
    247 			if (str == null || encoding == null) {
    248 				return str;
    249 			}
    250 			try {
    251 				return new String(str.getBytes(bytesEncoding), encoding);
    252 			} catch (final UnsupportedEncodingException e) {
    253 				throw new IllegalStateException(e);
    254 			}
    255 		}
    256
    257 	}
    258
    259 }