1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.cms.filters;
35
36 import info.magnolia.cms.beans.config.MIMEMapping;
37 import info.magnolia.cms.beans.config.ServerConfiguration;
38 import info.magnolia.cms.core.AggregationState;
39 import info.magnolia.cms.security.SecurityCallbackFilter;
40 import info.magnolia.cms.util.ServletUtil;
41 import info.magnolia.context.MgnlContext;
42 import info.magnolia.util.EscapeUtil;
43
44 import java.io.IOException;
45 import java.io.UnsupportedEncodingException;
46 import java.net.URI;
47 import java.net.URLDecoder;
48
49 import javax.inject.Inject;
50 import javax.servlet.FilterChain;
51 import javax.servlet.ServletException;
52 import javax.servlet.http.HttpServletRequest;
53 import javax.servlet.http.HttpServletResponse;
54
55 import org.apache.commons.lang3.StringUtils;
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 public class ContentTypeFilter extends AbstractMgnlFilter {
72
73 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ContentTypeFilter.class);
74
75 private boolean sanitizeXssUri = false;
76
77
78
79
80 private static final String AGGREGATION_STATE_INITIALIZED = ContentTypeFilter.class.getName() + ".aggregationStateInitialized";
81
82 private boolean registeredExtensionsOnly = false;
83 private boolean validateContentType = false;
84
85 private ServerConfiguration serverConfiguration;
86
87
88
89
90 @Deprecated
91 public ContentTypeFilter() {
92 }
93
94 @Inject
95 public ContentTypeFilter(ServerConfiguration serverConfiguration) {
96 this.serverConfiguration = serverConfiguration;
97 }
98
99
100 @Override
101 public void doFilter(HttpServletRequest request, HttpServletResponse originalResponse, FilterChain chain) throws IOException, ServletException {
102 final SecurityCallbackFilter.StatusSniffingResponseWrapper response;
103 if (originalResponse instanceof SecurityCallbackFilter.StatusSniffingResponseWrapper) {
104 response = (SecurityCallbackFilter.StatusSniffingResponseWrapper) originalResponse;
105 } else {
106 response = new SecurityCallbackFilter.StatusSniffingResponseWrapper(originalResponse);
107 }
108
109
110
111 if (request.getAttribute(AGGREGATION_STATE_INITIALIZED) != null) {
112 MgnlContext.resetAggregationState();
113 } else {
114 request.setAttribute(AGGREGATION_STATE_INITIALIZED, Boolean.TRUE);
115 }
116
117 final String originalUri = ServletUtil.getOriginalRequestURI(request);
118 final String originalUrl = ServletUtil.getOriginalRequestURLIncludingQueryString(request);
119 final String extension = getUriExtension(originalUri);
120 final String mimeType = getMimeType(extension, response);
121 if (isRegisteredExtensionsOnly() && mimeType == null && !response.isCommitted()) {
122 response.sendError(HttpServletResponse.SC_BAD_REQUEST, String.format("Unsupported extension=%1$s.", extension));
123 return;
124 }
125 final String characterEncoding = setupCharacterEncoding(mimeType, request, response);
126 final AggregationState aggregationState = MgnlContext.getAggregationState();
127 aggregationState.setCharacterEncoding(characterEncoding);
128
129 final String decodedOriginalUri = decodeUri(originalUri, characterEncoding);
130 aggregationState.setOriginalURI(decodedOriginalUri);
131
132 try {
133 final String decodedOriginalUrl = decodeUri(originalUrl, characterEncoding);
134 aggregationState.setOriginalURL(decodedOriginalUrl);
135 } catch (IllegalArgumentException e) {
136
137 if (!response.isCommitted()) {
138 response.sendError(HttpServletResponse.SC_BAD_REQUEST, "URL is malformed (not encoded properly).");
139 }
140
141 return;
142 }
143 aggregationState.setOriginalBrowserURI(originalUri);
144 aggregationState.setOriginalBrowserURL(originalUrl);
145 final String requestURI = URI.create(ServletUtil.getRequestUri(request)).normalize().getRawPath();
146 final String currentURI = decodeUri(requestURI, characterEncoding);
147 aggregationState.setCurrentURI(currentURI);
148 aggregationState.setExtension(extension);
149 aggregationState.setQueryString(request.getQueryString());
150
151 if (isValidateContentType()) {
152 ContentTypeCheckingResponseWrapper resWrapper = new ContentTypeCheckingResponseWrapper(response, extension);
153 chain.doFilter(request, resWrapper);
154 } else {
155 chain.doFilter(request, response);
156 }
157 if (response.getContentType() == null) {
158 if (response.getStatus() == HttpServletResponse.SC_OK) {
159 log.warn("Content type for {} is not set.", originalUrl);
160 if (!response.isCommitted()) {
161 log.warn("Response is not committed yet. Setting content type: {}.", mimeType);
162 response.setContentType(mimeType);
163 }
164 } else {
165 log.debug("Content type for {} is not set, status code of response is {}.", originalUrl, response.getStatus());
166 }
167 }
168 }
169
170 protected String getUriExtension(String uri) {
171 final String fileName = StringUtils.substringAfterLast(uri, "/");
172 return StringUtils.substringAfterLast(fileName, ".");
173 }
174
175 protected String getMimeType(String extension, HttpServletResponse response) {
176 final String mimeType;
177
178 if (isRegisteredExtensionsOnly()) {
179 if (StringUtils.isBlank(extension)) {
180 extension = serverConfiguration.getDefaultExtension();
181 if (StringUtils.isBlank(extension)) {
182 extension = MIMEMapping.DEFAULT_EXTENSION;
183 }
184 }
185 mimeType = MIMEMapping.getMIMEType(extension);
186 if (mimeType == null) {
187 return null;
188 }
189 } else {
190 mimeType = MIMEMapping.getMIMETypeOrDefault(extension);
191 }
192
193 return mimeType;
194 }
195
196 protected String setupCharacterEncoding(String mimeType, HttpServletRequest request, HttpServletResponse response) {
197 final String characterEncoding = MIMEMapping.getContentEncodingOrDefault(mimeType);
198
199 try {
200
201 if (request.getCharacterEncoding() == null) {
202 request.setCharacterEncoding(characterEncoding);
203 }
204 } catch (UnsupportedEncodingException e) {
205 log.error("Can't set character encoding for the request (mimetype={})", mimeType, e);
206 }
207
208 response.setCharacterEncoding(characterEncoding);
209
210 return characterEncoding;
211 }
212
213
214
215
216
217
218 private String sanitizeXss(String uri) {
219 final String afterLastDot = StringUtils.substringAfterLast(uri, ".");
220 if (StringUtils.isNotEmpty(afterLastDot)) {
221 final String xssEscapedAfterLastDot = EscapeUtil.escapeXss(afterLastDot);
222 final String sanitizedXssUri = StringUtils.removeEnd(uri, afterLastDot).concat(xssEscapedAfterLastDot);
223 return sanitizedXssUri;
224 }
225
226 return uri;
227 }
228
229
230
231
232 private String decodeUri(String uri, String characterEncoding) throws UnsupportedEncodingException {
233 final String decodedUri = URLDecoder.decode(uri, characterEncoding);
234 return isSanitizeXssUri() ? sanitizeXss(decodedUri) : decodedUri;
235 }
236
237
238
239
240 @Deprecated
241 public boolean isSanitizeXssUri() {
242 return sanitizeXssUri;
243 }
244
245
246
247
248 @Deprecated
249 public void setSanitizeXssUri(boolean sanitizeXssUri) {
250 this.sanitizeXssUri = sanitizeXssUri;
251 }
252
253 public boolean isRegisteredExtensionsOnly() {
254 return registeredExtensionsOnly;
255 }
256
257 public void setRegisteredExtensionsOnly(boolean registeredExtensionsOnly) {
258 this.registeredExtensionsOnly = registeredExtensionsOnly;
259 }
260
261 public boolean isValidateContentType() {
262 return validateContentType;
263 }
264
265 public void setValidateContentType(boolean validateContentType) {
266 this.validateContentType = validateContentType;
267 }
268
269
270
271
272 @Deprecated
273 protected String setupContentTypeAndCharacterEncoding(String extension, HttpServletRequest request, HttpServletResponse response) {
274 final String mimeType = setupContentType(extension, response);
275 return setupCharacterEncoding(mimeType, request, response);
276 }
277
278
279
280
281 @Deprecated
282 protected String setupContentType(String extension, HttpServletResponse response) {
283 return getMimeType(extension, response);
284 }
285 }