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 }