1   /**
2    * Copyright (c) 2000-2009 Liferay, Inc. All rights reserved.
3    *
4    * The contents of this file are subject to the terms of the Liferay Enterprise
5    * Subscription License ("License"). You may not use this file except in
6    * compliance with the License. You can obtain a copy of the License by
7    * contacting Liferay, Inc. See the License for the specific language governing
8    * permissions and limitations under the License, including but not limited to
9    * distribution rights of the Software.
10   *
11   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17   * SOFTWARE.
18   */
19  
20  package com.liferay.portal.servlet.filters.virtualhost;
21  
22  import com.liferay.portal.LayoutFriendlyURLException;
23  import com.liferay.portal.kernel.log.Log;
24  import com.liferay.portal.kernel.log.LogFactoryUtil;
25  import com.liferay.portal.kernel.util.StringPool;
26  import com.liferay.portal.kernel.util.StringUtil;
27  import com.liferay.portal.kernel.util.Validator;
28  import com.liferay.portal.model.Group;
29  import com.liferay.portal.model.LayoutSet;
30  import com.liferay.portal.model.impl.LayoutImpl;
31  import com.liferay.portal.service.GroupLocalServiceUtil;
32  import com.liferay.portal.servlet.AbsoluteRedirectsResponse;
33  import com.liferay.portal.servlet.I18nServlet;
34  import com.liferay.portal.servlet.filters.BasePortalFilter;
35  import com.liferay.portal.struts.LastPath;
36  import com.liferay.portal.util.PortalInstances;
37  import com.liferay.portal.util.PortalUtil;
38  import com.liferay.portal.util.PropsValues;
39  import com.liferay.portal.util.WebKeys;
40  
41  import java.io.IOException;
42  
43  import java.util.Set;
44  
45  import javax.servlet.FilterChain;
46  import javax.servlet.FilterConfig;
47  import javax.servlet.RequestDispatcher;
48  import javax.servlet.ServletContext;
49  import javax.servlet.ServletException;
50  import javax.servlet.ServletRequest;
51  import javax.servlet.ServletResponse;
52  import javax.servlet.http.HttpServletRequest;
53  import javax.servlet.http.HttpServletResponse;
54  import javax.servlet.http.HttpSession;
55  
56  /**
57   * <a href="VirtualHostFilter.java.html"><b><i>View Source</i></b></a>
58   *
59   * <p>
60   * This filter is used to provide virtual host functionality. However, this
61   * filter is still required even if you do not use virtual hosting because it
62   * sets the company id in the request so that subsequent calls in the thread
63   * have the company id properly set. This filter must also always be the first
64   * filter in the list of filters.
65   * </p>
66   *
67   * @author Joel Kozikowski
68   * @author Brian Wing Shun Chan
69   * @author Raymond Augé
70   *
71   */
72  public class VirtualHostFilter extends BasePortalFilter {
73  
74      public void init(FilterConfig filterConfig) {
75          super.init(filterConfig);
76  
77          _servletContext = filterConfig.getServletContext();
78      }
79  
80      public void doFilter(
81              ServletRequest servletRequest, ServletResponse servletResponse,
82              FilterChain filterChain)
83          throws IOException, ServletException {
84  
85          if (_log.isDebugEnabled()) {
86              if (isFilterEnabled()) {
87                  _log.debug(VirtualHostFilter.class + " is enabled");
88              }
89              else {
90                  _log.debug(VirtualHostFilter.class + " is disabled");
91              }
92          }
93  
94          HttpServletRequest request = (HttpServletRequest)servletRequest;
95          HttpServletResponse response = (HttpServletResponse)servletResponse;
96  
97          request.setCharacterEncoding(StringPool.UTF8);
98          //response.setContentType(ContentTypes.TEXT_HTML_UTF8);
99  
100         // Make sure all redirects issued by the portal are absolute
101 
102         response = new AbsoluteRedirectsResponse(request, response);
103 
104         // Company id needs to always be called here so that it's properly set
105         // in subsequent calls
106 
107         long companyId = PortalInstances.getCompanyId(request);
108 
109         if (_log.isDebugEnabled()) {
110             _log.debug("Company id " + companyId);
111         }
112 
113         PortalUtil.getCurrentURL(request);
114 
115         HttpSession session = request.getSession();
116 
117         Boolean httpsInitial = (Boolean)session.getAttribute(
118             WebKeys.HTTPS_INITIAL);
119 
120         if (httpsInitial == null) {
121             httpsInitial = Boolean.valueOf(request.isSecure());
122 
123             session.setAttribute(WebKeys.HTTPS_INITIAL, httpsInitial);
124 
125             if (_log.isDebugEnabled()) {
126                 _log.debug("Setting httpsInitial to " + httpsInitial);
127             }
128         }
129 
130         if (!isFilterEnabled()) {
131             processFilter(
132                 VirtualHostFilter.class, request, response, filterChain);
133 
134             return;
135         }
136 
137         StringBuffer requestURL = request.getRequestURL();
138 
139         if (_log.isDebugEnabled()) {
140             _log.debug("Received " + requestURL);
141         }
142 
143         if (!isValidRequestURL(requestURL)) {
144             processFilter(
145                 VirtualHostFilter.class, request, response, filterChain);
146 
147             return;
148         }
149 
150         String contextPath = PortalUtil.getPathContext();
151 
152         String friendlyURL = request.getRequestURI();
153 
154         if ((Validator.isNotNull(contextPath)) &&
155             (friendlyURL.indexOf(contextPath) != -1)) {
156 
157             friendlyURL = friendlyURL.substring(contextPath.length());
158         }
159 
160         friendlyURL = StringUtil.replace(
161             friendlyURL, StringPool.DOUBLE_SLASH, StringPool.SLASH);
162 
163         String i18nLanguageId = null;
164 
165         Set<String> languageIds = I18nServlet.getLanguageIds();
166 
167         for (String languageId : languageIds) {
168             if (friendlyURL.startsWith(languageId)) {
169                 int pos = friendlyURL.indexOf(StringPool.SLASH, 1);
170 
171                 i18nLanguageId = friendlyURL.substring(0, pos);
172                 friendlyURL = friendlyURL.substring(pos);
173 
174                 break;
175             }
176         }
177 
178         if (_log.isDebugEnabled()) {
179             _log.debug("Friendly URL " + friendlyURL);
180         }
181 
182         if (!isValidFriendlyURL(friendlyURL)) {
183             _log.debug("Friendly URL is not valid");
184 
185             processFilter(
186                 VirtualHostFilter.class, request, response, filterChain);
187 
188             return;
189         }
190 
191         LayoutSet layoutSet = (LayoutSet)servletRequest.getAttribute(
192             WebKeys.VIRTUAL_HOST_LAYOUT_SET);
193 
194         if (_log.isDebugEnabled()) {
195             _log.debug("Layout set " + layoutSet);
196         }
197 
198         if (layoutSet != null) {
199             try {
200                 LastPath lastPath = new LastPath(
201                     contextPath, friendlyURL, servletRequest.getParameterMap());
202 
203                 servletRequest.setAttribute(WebKeys.LAST_PATH, lastPath);
204 
205                 StringBuilder prefix = new StringBuilder();
206 
207                 Group group = GroupLocalServiceUtil.getGroup(
208                     layoutSet.getGroupId());
209 
210                 if (layoutSet.isPrivateLayout()) {
211                     if (group.isUser()) {
212                         prefix.append(_PRIVATE_USER_SERVLET_MAPPING);
213                     }
214                     else {
215                         prefix.append(_PRIVATE_GROUP_SERVLET_MAPPING);
216                     }
217                 }
218                 else {
219                     prefix.append(_PUBLIC_GROUP_SERVLET_MAPPING);
220                 }
221 
222                 prefix.append(group.getFriendlyURL());
223 
224                 StringBuilder redirect = new StringBuilder();
225 
226                 if (i18nLanguageId != null) {
227                     redirect.append(i18nLanguageId);
228                 }
229 
230                 if (friendlyURL.startsWith(
231                         PropsValues.WIDGET_SERVLET_MAPPING)) {
232 
233                     redirect.append(PropsValues.WIDGET_SERVLET_MAPPING);
234 
235                     friendlyURL = StringUtil.replaceFirst(
236                         friendlyURL, PropsValues.WIDGET_SERVLET_MAPPING,
237                         StringPool.BLANK);
238                 }
239 
240                 long plid = PortalUtil.getPlidFromFriendlyURL(
241                     companyId, friendlyURL);
242 
243                 if (plid <= 0) {
244                     redirect.append(prefix);
245                 }
246 
247                 redirect.append(friendlyURL);
248 
249                 String query = request.getQueryString();
250 
251                 if (query != null) {
252                     redirect.append(StringPool.QUESTION);
253                     redirect.append(query);
254                 }
255 
256                 if (_log.isDebugEnabled()) {
257                     _log.debug("Redirect to " + redirect);
258                 }
259 
260                 RequestDispatcher requestDispatcher =
261                     _servletContext.getRequestDispatcher(redirect.toString());
262 
263                 requestDispatcher.forward(servletRequest, response);
264 
265                 return;
266             }
267             catch (Exception e) {
268                 _log.error(e, e);
269             }
270         }
271 
272         processFilter(VirtualHostFilter.class, request, response, filterChain);
273     }
274 
275     protected boolean isValidFriendlyURL(String friendlyURL) {
276         friendlyURL = friendlyURL.toLowerCase();
277 
278         if (PortalInstances.isVirtualHostsIgnorePath(friendlyURL) ||
279             friendlyURL.startsWith(
280                 PortalUtil.getPathFriendlyURLPrivateGroup() +
281                     StringPool.SLASH) ||
282             friendlyURL.startsWith(
283                 PortalUtil.getPathFriendlyURLPublic() + StringPool.SLASH) ||
284             friendlyURL.startsWith(
285                 PortalUtil.getPathFriendlyURLPrivateUser() +
286                     StringPool.SLASH) ||
287             friendlyURL.startsWith(_PATH_C) ||
288             friendlyURL.startsWith(_PATH_DELEGATE) ||
289             friendlyURL.startsWith(_PATH_HTML) ||
290             friendlyURL.startsWith(_PATH_IMAGE) ||
291             friendlyURL.startsWith(_PATH_LANGUAGE) ||
292             friendlyURL.startsWith(_PATH_SITEMAP_XML) ||
293             friendlyURL.startsWith(_PATH_SOFTWARE_CATALOG) ||
294             friendlyURL.startsWith(_PATH_WAP) ||
295             friendlyURL.startsWith(_PATH_WSRP)) {
296 
297             return false;
298         }
299 
300         int code = LayoutImpl.validateFriendlyURL(friendlyURL);
301 
302         if ((code > -1) &&
303             (code != LayoutFriendlyURLException.ENDS_WITH_SLASH)) {
304 
305             return false;
306         }
307 
308         return true;
309     }
310 
311     protected boolean isValidRequestURL(StringBuffer requestURL) {
312         if (requestURL == null) {
313             return false;
314         }
315 
316         String url = requestURL.toString();
317 
318         if (url.endsWith(_EXT_C) || url.endsWith(_EXT_CSS) ||
319             url.endsWith(_EXT_GIF) || url.endsWith(_EXT_IMAGE_COMPANY_LOGO) ||
320             url.endsWith(_EXT_ICO) || url.endsWith(_EXT_JS) ||
321             url.endsWith(_EXT_JPEG) || url.endsWith(_EXT_JSP) ||
322             url.endsWith(_EXT_PORTAL_LAYOUT) ||
323             url.endsWith(_EXT_PORTAL_LOGIN) ||
324             url.endsWith(_EXT_PORTAL_LOGOUT) || url.endsWith(_EXT_PNG)) {
325 
326             return false;
327         }
328         else {
329             return true;
330         }
331     }
332 
333     protected void processFilter(
334         HttpServletRequest request, HttpServletResponse response,
335         FilterChain filterChain) {
336     }
337 
338     private static Log _log = LogFactoryUtil.getLog(VirtualHostFilter.class);
339 
340     private static String _EXT_C = "/c";
341 
342     private static String _EXT_CSS = ".css";
343 
344     private static String _EXT_GIF = ".gif";
345 
346     private static String _EXT_IMAGE_COMPANY_LOGO = "/image/company_logo";
347 
348     private static String _EXT_ICO = ".ico";
349 
350     private static String _EXT_JS = ".js";
351 
352     private static String _EXT_JPEG = ".jpeg";
353 
354     private static String _EXT_JSP = ".jsp";
355 
356     private static String _EXT_PORTAL_LAYOUT = "/portal/layout";
357 
358     private static String _EXT_PORTAL_LOGIN = "/portal/login";
359 
360     private static String _EXT_PORTAL_LOGOUT = "/portal/logout";
361 
362     private static String _EXT_PNG = ".png";
363 
364     private static String _PATH_C = "/c/";
365 
366     private static String _PATH_DELEGATE = "/delegate/";
367 
368     private static String _PATH_HTML = "/html/";
369 
370     private static String _PATH_IMAGE = "/image/";
371 
372     private static String _PATH_LANGUAGE = "/language/";
373 
374     private static String _PATH_SITEMAP_XML = "/sitemap.xml";
375 
376     private static String _PATH_SOFTWARE_CATALOG = "/software_catalog/";
377 
378     private static String _PATH_WAP = "/wap/";
379 
380     private static String _PATH_WSRP = "/wsrp/";
381 
382     private static String _PRIVATE_GROUP_SERVLET_MAPPING =
383         PropsValues.LAYOUT_FRIENDLY_URL_PRIVATE_GROUP_SERVLET_MAPPING;
384 
385     private static String _PRIVATE_USER_SERVLET_MAPPING =
386         PropsValues.LAYOUT_FRIENDLY_URL_PRIVATE_USER_SERVLET_MAPPING;
387 
388     private static String _PUBLIC_GROUP_SERVLET_MAPPING =
389         PropsValues.LAYOUT_FRIENDLY_URL_PUBLIC_SERVLET_MAPPING;
390 
391     private ServletContext _servletContext;
392 
393 }