View Javadoc

1   /*
2    * Copyright 2004-2008 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>
53   * が指定された場合はこのエンコーディングがリクエストに設定されます。</td>
54   * </tr>
55   * <tr>
56   * <td>{@value #FORCE_ENCODING}</td>
57   * <td><code>true</code> を指定した場合は、リクエストにエンコーディングが設定されていても {@value #ENCODING}
58   * で上書きします。
59   * </tr>
60   * <tr>
61   * <td>{@value #URI_ENCODING}</td>
62   * <td>URI のエンコーディングを指定します。</td>
63   * </tr>
64   * <tr>
65   * <td>{@value #URI_BYTES_ENCODING}</td>
66   * <td>URI をバイト配列として取得する際のエンコーディングを指定します。</td>
67   * </tr>
68   * </tbody>
69   * </table>
70   * <caption>初期化パラメータ</caption>
71   * </p>
72   * 
73   * @author baba
74   * @since 1.1.1
75   */
76  public class EncodingFilter implements Filter {
77  
78  	private static final String ALREADY_FILTERED_ATTRIBUTE_NAME = EncodingFilter.class
79  			.getName()
80  			+ ".FILTERED";
81  
82  	/** エンコーディングのキー。 */
83  	private static final String ENCODING = "encoding";
84  
85  	/** 強制エンコーディング設定のキー。 */
86  	private static final String FORCE_ENCODING = "forceEncoding";
87  
88  	/** URI エンコーディングのキー。 */
89  	private static final String URI_ENCODING = "URIEncoding";
90  
91  	/** URI バイト列のエンコーディングのキー。 */
92  	private static final String URI_BYTES_ENCODING = "URIBytesEncoding";
93  
94  	/** URI バイト列のエンコーディングのデフォルト値。 */
95  	private static final String DEFAULT_URI_BYTE_ENCODING = "ISO-8859-1";
96  
97  	/** エンコーディング。 */
98  	private String encoding;
99  
100 	/** 強制エンコーディング設定。 */
101 	private boolean forceEncoding = false;
102 
103 	/** URI エンコーディング。 */
104 	private String uriEncoding;
105 
106 	/** URI バイト列のエンコーディング。 */
107 	private String uriBytesEncoding;
108 
109 	/**
110 	 * {@inheritDoc}
111 	 */
112 	public void init(final FilterConfig config) throws ServletException {
113 		encoding = config.getInitParameter(ENCODING);
114 		try {
115 			validateEncoding(encoding);
116 		} catch (final UnsupportedEncodingException e) {
117 			throw new ServletException(e);
118 		}
119 
120 		final String forceEncodingString = config
121 				.getInitParameter(FORCE_ENCODING);
122 		if (forceEncodingString != null) {
123 			forceEncoding = Boolean.parseBoolean(forceEncodingString);
124 		}
125 
126 		uriEncoding = config.getInitParameter(URI_ENCODING);
127 		try {
128 			validateEncoding(uriEncoding);
129 		} catch (final UnsupportedEncodingException e) {
130 			throw new ServletException(e);
131 		}
132 
133 		uriBytesEncoding = config.getInitParameter(URI_BYTES_ENCODING);
134 		if (uriBytesEncoding == null) {
135 			uriBytesEncoding = DEFAULT_URI_BYTE_ENCODING;
136 		}
137 		try {
138 			validateEncoding(uriBytesEncoding);
139 		} catch (final UnsupportedEncodingException e) {
140 			throw new ServletException(e);
141 		}
142 	}
143 
144 	/**
145 	 * 指定されたエンコーディングがサポートされているか検査します。
146 	 * 
147 	 * @param encoding
148 	 *            エンコーディング
149 	 * @throws UnsupportedEncodingException
150 	 *             指定されたエンコーディングがサポートされていない場合
151 	 */
152 	private void validateEncoding(final String encoding)
153 			throws UnsupportedEncodingException {
154 		if (encoding != null && !Charset.isSupported(encoding)) {
155 			throw new UnsupportedEncodingException(encoding);
156 		}
157 	}
158 
159 	/**
160 	 * {@inheritDoc}
161 	 */
162 	public void destroy() {
163 	}
164 
165 	/**
166 	 * {@inheritDoc}
167 	 */
168 	public void doFilter(final ServletRequest request,
169 			final ServletResponse response, final FilterChain chain)
170 			throws IOException, ServletException {
171 		if (request.getAttribute(ALREADY_FILTERED_ATTRIBUTE_NAME) == null) {
172 			request.setAttribute(ALREADY_FILTERED_ATTRIBUTE_NAME, Boolean.TRUE);
173 			if (request.getCharacterEncoding() == null || forceEncoding) {
174 				request.setCharacterEncoding(encoding);
175 			}
176 			if (uriEncoding == null) {
177 				chain.doFilter(request, response);
178 			} else {
179 				final ServletRequest wrapper = new EncodingHttpServletRequestWrapper(
180 						HttpServletRequest.class.cast(request), uriEncoding,
181 						uriBytesEncoding);
182 				chain.doFilter(wrapper, response);
183 			}
184 			request.removeAttribute(ALREADY_FILTERED_ATTRIBUTE_NAME);
185 		} else {
186 			chain.doFilter(request, response);
187 		}
188 	}
189 
190 	/**
191 	 * 適切なエンコーディングで処理するための {@link HttpServletRequestWrapper} です。
192 	 * 
193 	 * @author baba
194 	 * @since 1.1.1
195 	 */
196 	private class EncodingHttpServletRequestWrapper extends
197 			HttpServletRequestWrapper {
198 
199 		/** URI エンコーディング。 */
200 		private final String uriEncoding;
201 
202 		/** URI バイト列のエンコーディング。 */
203 		private final String uriBytesEncoding;
204 
205 		/**
206 		 * 指定されたリクエストをラップします。
207 		 * 
208 		 * @param request
209 		 *            リクエスト
210 		 * @param uriEncoding
211 		 *            URI エンコーディング
212 		 * @param uriBytesEncoding
213 		 *            URI バイト列のエンコーディング。
214 		 * @throws IOException
215 		 *             スーパクラスのコンストラクタで例外が発生した場合
216 		 */
217 		public EncodingHttpServletRequestWrapper(
218 				final HttpServletRequest request, final String uriEncoding,
219 				final String uriBytesEncoding) throws IOException {
220 			super(request);
221 			this.uriEncoding = uriEncoding;
222 			this.uriBytesEncoding = uriBytesEncoding;
223 		}
224 
225 		@Override
226 		public String getServletPath() {
227 			return rebuild(super.getServletPath(), uriEncoding,
228 					uriBytesEncoding);
229 		}
230 
231 		@Override
232 		public String getPathInfo() {
233 			return rebuild(super.getPathInfo(), uriEncoding, uriBytesEncoding);
234 		}
235 
236 		/**
237 		 * 指定された文字列を一度 <code>bytesEncoding</code> でバイト配列に戻し、
238 		 * <code>encoding</code> で文字列を再構築します。
239 		 * 
240 		 * @param str
241 		 *            文字列
242 		 * @param encoding
243 		 *            エンコーディング
244 		 * @param bytesEncoding
245 		 *            バイト配列に戻す時のエンコーディング
246 		 * @return 再構築された文字列
247 		 */
248 		private String rebuild(final String str, final String encoding,
249 				final String bytesEncoding) {
250 			if (str == null || encoding == null) {
251 				return str;
252 			}
253 			try {
254 				return new String(str.getBytes(bytesEncoding), encoding);
255 			} catch (final UnsupportedEncodingException e) {
256 				throw new IllegalStateException(e);
257 			}
258 		}
259 
260 	}
261 
262 }