View Javadoc

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