1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * This library is free software; you can redistribute it and/or modify it under
5    * the terms of the GNU Lesser General Public License as published by the Free
6    * Software Foundation; either version 2.1 of the License, or (at your option)
7    * any later version.
8    *
9    * This library is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11   * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12   * details.
13   */
14  
15  package com.liferay.portal.servlet.filters.virtualhost;
16  
17  import com.liferay.portal.LayoutFriendlyURLException;
18  import com.liferay.portal.kernel.log.Log;
19  import com.liferay.portal.kernel.log.LogFactoryUtil;
20  import com.liferay.portal.kernel.struts.LastPath;
21  import com.liferay.portal.kernel.util.StringBundler;
22  import com.liferay.portal.kernel.util.StringPool;
23  import com.liferay.portal.kernel.util.StringUtil;
24  import com.liferay.portal.kernel.util.Validator;
25  import com.liferay.portal.model.Group;
26  import com.liferay.portal.model.LayoutSet;
27  import com.liferay.portal.model.impl.LayoutImpl;
28  import com.liferay.portal.service.GroupLocalServiceUtil;
29  import com.liferay.portal.servlet.AbsoluteRedirectsResponse;
30  import com.liferay.portal.servlet.I18nServlet;
31  import com.liferay.portal.servlet.filters.BasePortalFilter;
32  import com.liferay.portal.util.PortalInstances;
33  import com.liferay.portal.util.PortalUtil;
34  import com.liferay.portal.util.PropsValues;
35  import com.liferay.portal.util.WebKeys;
36  
37  import java.util.Set;
38  
39  import javax.servlet.FilterChain;
40  import javax.servlet.FilterConfig;
41  import javax.servlet.RequestDispatcher;
42  import javax.servlet.ServletContext;
43  import javax.servlet.http.HttpServletRequest;
44  import javax.servlet.http.HttpServletResponse;
45  import javax.servlet.http.HttpSession;
46  
47  /**
48   * <a href="VirtualHostFilter.java.html"><b><i>View Source</i></b></a>
49   *
50   * <p>
51   * This filter is used to provide virtual host functionality. However, this
52   * filter is still required even if you do not use virtual hosting because it
53   * sets the company id in the request so that subsequent calls in the thread
54   * have the company id properly set. This filter must also always be the first
55   * filter in the list of filters.
56   * </p>
57   *
58   * @author Joel Kozikowski
59   * @author Brian Wing Shun Chan
60   * @author Raymond Augé
61   * @author Eduardo Lundgren
62   */
63  public class VirtualHostFilter extends BasePortalFilter {
64  
65      public void init(FilterConfig filterConfig) {
66          super.init(filterConfig);
67  
68          _servletContext = filterConfig.getServletContext();
69      }
70  
71      protected boolean isValidFriendlyURL(String friendlyURL) {
72          friendlyURL = friendlyURL.toLowerCase();
73  
74          if (PortalInstances.isVirtualHostsIgnorePath(friendlyURL) ||
75              friendlyURL.startsWith(
76                  PortalUtil.getPathFriendlyURLPrivateGroup() +
77                      StringPool.SLASH) ||
78              friendlyURL.startsWith(
79                  PortalUtil.getPathFriendlyURLPublic() + StringPool.SLASH) ||
80              friendlyURL.startsWith(
81                  PortalUtil.getPathFriendlyURLPrivateUser() +
82                      StringPool.SLASH) ||
83              friendlyURL.startsWith(_PATH_C) ||
84              friendlyURL.startsWith(_PATH_COMBO) ||
85              friendlyURL.startsWith(_PATH_DELEGATE) ||
86              friendlyURL.startsWith(_PATH_DISPLAY_CHART) ||
87              friendlyURL.startsWith(_PATH_DOCUMENTS) ||
88              friendlyURL.startsWith(_PATH_DTD) ||
89              friendlyURL.startsWith(_PATH_FACEBOOK) ||
90              friendlyURL.startsWith(_PATH_GOOGLE_GADGET) ||
91              friendlyURL.startsWith(_PATH_HTML) ||
92              friendlyURL.startsWith(_PATH_IMAGE) ||
93              friendlyURL.startsWith(_PATH_LANGUAGE) ||
94              friendlyURL.startsWith(_PATH_NETVIBES) ||
95              friendlyURL.startsWith(_PATH_PBHS) ||
96              friendlyURL.startsWith(_PATH_POLLER) ||
97              friendlyURL.startsWith(_PATH_SHAREPOINT) ||
98              friendlyURL.startsWith(_PATH_SITEMAP_XML) ||
99              friendlyURL.startsWith(_PATH_SOFTWARE_CATALOG) ||
100             friendlyURL.startsWith(_PATH_VTI) ||
101             friendlyURL.startsWith(_PATH_WAP) ||
102             friendlyURL.startsWith(_PATH_WIDGET) ||
103             friendlyURL.startsWith(_PATH_XMLRPC)) {
104 
105             return false;
106         }
107 
108         int code = LayoutImpl.validateFriendlyURL(friendlyURL);
109 
110         if ((code > -1) &&
111             (code != LayoutFriendlyURLException.ENDS_WITH_SLASH)) {
112 
113             return false;
114         }
115 
116         return true;
117     }
118 
119     protected boolean isValidRequestURL(StringBuffer requestURL) {
120         if (requestURL == null) {
121             return false;
122         }
123 
124         String url = requestURL.toString();
125 
126         for (String extension : PropsValues.VIRTUAL_HOSTS_IGNORE_EXTENSIONS) {
127             if (url.endsWith(extension)) {
128                 return false;
129             }
130         }
131 
132         return true;
133     }
134 
135     protected void processFilter(
136             HttpServletRequest request, HttpServletResponse response,
137             FilterChain filterChain)
138         throws Exception {
139 
140         request.setCharacterEncoding(StringPool.UTF8);
141         //response.setContentType(ContentTypes.TEXT_HTML_UTF8);
142 
143         // Make sure all redirects issued by the portal are absolute
144 
145         response = new AbsoluteRedirectsResponse(request, response);
146 
147         // Company id needs to always be called here so that it's properly set
148         // in subsequent calls
149 
150         long companyId = PortalInstances.getCompanyId(request);
151 
152         if (_log.isDebugEnabled()) {
153             _log.debug("Company id " + companyId);
154         }
155 
156         PortalUtil.getCurrentCompleteURL(request);
157         PortalUtil.getCurrentURL(request);
158 
159         HttpSession session = request.getSession();
160 
161         Boolean httpsInitial = (Boolean)session.getAttribute(
162             WebKeys.HTTPS_INITIAL);
163 
164         if (httpsInitial == null) {
165             httpsInitial = Boolean.valueOf(request.isSecure());
166 
167             session.setAttribute(WebKeys.HTTPS_INITIAL, httpsInitial);
168 
169             if (_log.isDebugEnabled()) {
170                 _log.debug("Setting httpsInitial to " + httpsInitial);
171             }
172         }
173 
174         if (!isFilterEnabled()) {
175             processFilter(
176                 VirtualHostFilter.class, request, response, filterChain);
177 
178             return;
179         }
180 
181         StringBuffer requestURL = request.getRequestURL();
182 
183         if (_log.isDebugEnabled()) {
184             _log.debug("Received " + requestURL);
185         }
186 
187         if (!isValidRequestURL(requestURL)) {
188             processFilter(
189                 VirtualHostFilter.class, request, response, filterChain);
190 
191             return;
192         }
193 
194         String contextPath = PortalUtil.getPathContext();
195 
196         String friendlyURL = request.getRequestURI();
197 
198         if ((Validator.isNotNull(contextPath)) &&
199             (friendlyURL.indexOf(contextPath) != -1)) {
200 
201             friendlyURL = friendlyURL.substring(contextPath.length());
202         }
203 
204         friendlyURL = StringUtil.replace(
205             friendlyURL, StringPool.DOUBLE_SLASH, StringPool.SLASH);
206 
207         String i18nLanguageId = null;
208 
209         Set<String> languageIds = I18nServlet.getLanguageIds();
210 
211         for (String languageId : languageIds) {
212             if (friendlyURL.startsWith(languageId)) {
213                 int pos = friendlyURL.indexOf(StringPool.SLASH, 1);
214 
215                 if (((pos != -1) && (pos != languageId.length())) ||
216                     ((pos == -1) && !friendlyURL.equals(languageId))) {
217 
218                     continue;
219                 }
220 
221                 if (pos == -1) {
222                     i18nLanguageId = friendlyURL;
223                     friendlyURL = StringPool.SLASH;
224                 }
225                 else {
226                     i18nLanguageId = friendlyURL.substring(0, pos);
227                     friendlyURL = friendlyURL.substring(pos);
228                 }
229 
230                 break;
231             }
232         }
233 
234         if (_log.isDebugEnabled()) {
235             _log.debug("Friendly URL " + friendlyURL);
236         }
237 
238         if (!friendlyURL.equals(StringPool.SLASH) &&
239             !isValidFriendlyURL(friendlyURL)) {
240 
241             _log.debug("Friendly URL is not valid");
242 
243             processFilter(
244                 VirtualHostFilter.class, request, response, filterChain);
245 
246             return;
247         }
248 
249         LayoutSet layoutSet = (LayoutSet)request.getAttribute(
250             WebKeys.VIRTUAL_HOST_LAYOUT_SET);
251 
252         if (_log.isDebugEnabled()) {
253             _log.debug("Layout set " + layoutSet);
254         }
255 
256         if (layoutSet != null) {
257             try {
258                 LastPath lastPath = new LastPath(
259                     contextPath, friendlyURL, request.getParameterMap());
260 
261                 request.setAttribute(WebKeys.LAST_PATH, lastPath);
262 
263                 StringBundler prefix = new StringBundler(2);
264 
265                 Group group = GroupLocalServiceUtil.getGroup(
266                     layoutSet.getGroupId());
267 
268                 if (layoutSet.isPrivateLayout()) {
269                     if (group.isUser()) {
270                         prefix.append(_PRIVATE_USER_SERVLET_MAPPING);
271                     }
272                     else {
273                         prefix.append(_PRIVATE_GROUP_SERVLET_MAPPING);
274                     }
275                 }
276                 else {
277                     prefix.append(_PUBLIC_GROUP_SERVLET_MAPPING);
278                 }
279 
280                 prefix.append(group.getFriendlyURL());
281 
282                 StringBundler forwardURL = new StringBundler(6);
283 
284                 if (i18nLanguageId != null) {
285                     forwardURL.append(i18nLanguageId);
286                 }
287 
288                 if (friendlyURL.startsWith(
289                         PropsValues.WIDGET_SERVLET_MAPPING)) {
290 
291                     forwardURL.append(PropsValues.WIDGET_SERVLET_MAPPING);
292 
293                     friendlyURL = StringUtil.replaceFirst(
294                         friendlyURL, PropsValues.WIDGET_SERVLET_MAPPING,
295                         StringPool.BLANK);
296                 }
297 
298                 long plid = PortalUtil.getPlidFromFriendlyURL(
299                     companyId, friendlyURL);
300 
301                 if (plid <= 0) {
302                     forwardURL.append(prefix);
303                 }
304 
305                 forwardURL.append(friendlyURL);
306 
307                 if (_log.isDebugEnabled()) {
308                     _log.debug("Forward to " + forwardURL);
309                 }
310 
311                 RequestDispatcher requestDispatcher =
312                     _servletContext.getRequestDispatcher(forwardURL.toString());
313 
314                 requestDispatcher.forward(request, response);
315 
316                 return;
317             }
318             catch (Exception e) {
319                 _log.error(e, e);
320             }
321         }
322 
323         processFilter(VirtualHostFilter.class, request, response, filterChain);
324     }
325 
326     private static final String _PATH_C = "/c/";
327 
328     private static final String _PATH_COMBO = "/combo/";
329 
330     private static final String _PATH_DELEGATE = "/delegate/";
331 
332     private static final String _PATH_DISPLAY_CHART = "/display_chart";
333 
334     private static final String _PATH_DOCUMENTS = "/documents/";
335 
336     private static final String _PATH_DTD = "/dtd/";
337 
338     private static final String _PATH_FACEBOOK = "/facebook/";
339 
340     private static final String _PATH_GOOGLE_GADGET = "/google_gadget/";
341 
342     private static final String _PATH_HTML = "/html/";
343 
344     private static final String _PATH_IMAGE = "/image/";
345 
346     private static final String _PATH_LANGUAGE = "/language/";
347 
348     private static final String _PATH_NETVIBES = "/netvibes/";
349 
350     private static final String _PATH_PBHS = "/pbhs/";
351 
352     private static final String _PATH_POLLER = "/poller/";
353 
354     private static final String _PATH_SHAREPOINT = "/sharepoint/";
355 
356     private static final String _PATH_SITEMAP_XML = "/sitemap.xml";
357 
358     private static final String _PATH_SOFTWARE_CATALOG = "/software_catalog";
359 
360     private static final String _PATH_VTI = "/_vti_";
361 
362     private static final String _PATH_WAP = "/wap/";
363 
364     private static final String _PATH_WIDGET = "/widget/";
365 
366     private static final String _PATH_XMLRPC = "/xmlrpc/";
367 
368     private static final String _PRIVATE_GROUP_SERVLET_MAPPING =
369         PropsValues.LAYOUT_FRIENDLY_URL_PRIVATE_GROUP_SERVLET_MAPPING;
370 
371     private static final String _PRIVATE_USER_SERVLET_MAPPING =
372         PropsValues.LAYOUT_FRIENDLY_URL_PRIVATE_USER_SERVLET_MAPPING;
373 
374     private static final String _PUBLIC_GROUP_SERVLET_MAPPING =
375         PropsValues.LAYOUT_FRIENDLY_URL_PUBLIC_SERVLET_MAPPING;
376 
377     private static Log _log = LogFactoryUtil.getLog(VirtualHostFilter.class);
378 
379     private ServletContext _servletContext;
380 
381 }