001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
013     */
014    
015    package com.liferay.portal.servlet;
016    
017    import com.liferay.portal.kernel.cache.PortalCache;
018    import com.liferay.portal.kernel.cache.SingleVMPoolUtil;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.servlet.HttpHeaders;
022    import com.liferay.portal.kernel.servlet.ServletContextUtil;
023    import com.liferay.portal.kernel.servlet.ServletResponseUtil;
024    import com.liferay.portal.kernel.util.CharPool;
025    import com.liferay.portal.kernel.util.ContentTypes;
026    import com.liferay.portal.kernel.util.FileUtil;
027    import com.liferay.portal.kernel.util.ParamUtil;
028    import com.liferay.portal.kernel.util.PropsKeys;
029    import com.liferay.portal.kernel.util.StringPool;
030    import com.liferay.portal.kernel.util.StringUtil;
031    import com.liferay.portal.kernel.util.Time;
032    import com.liferay.portal.kernel.util.Validator;
033    import com.liferay.portal.servlet.filters.dynamiccss.DynamicCSSUtil;
034    import com.liferay.portal.util.MinifierUtil;
035    import com.liferay.portal.util.PortalUtil;
036    import com.liferay.portal.util.PrefsPropsUtil;
037    import com.liferay.portal.util.PropsValues;
038    
039    import java.io.File;
040    import java.io.IOException;
041    import java.io.Serializable;
042    
043    import java.util.Arrays;
044    import java.util.HashSet;
045    import java.util.Set;
046    
047    import javax.servlet.ServletContext;
048    import javax.servlet.ServletException;
049    import javax.servlet.http.HttpServlet;
050    import javax.servlet.http.HttpServletRequest;
051    import javax.servlet.http.HttpServletResponse;
052    
053    /**
054     * @author Eduardo Lundgren
055     * @author Edward Han
056     * @author Zsigmond Rab
057     */
058    public class ComboServlet extends HttpServlet {
059    
060            @Override
061            public void service(
062                            HttpServletRequest request, HttpServletResponse response)
063                    throws IOException, ServletException {
064    
065                    try {
066                            doService(request, response);
067                    }
068                    catch (Exception e) {
069                            _log.error(e, e);
070    
071                            PortalUtil.sendError(
072                                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e, request,
073                                    response);
074                    }
075            }
076    
077            protected void doService(
078                            HttpServletRequest request, HttpServletResponse response)
079                    throws Exception {
080    
081                    String contextPath = PortalUtil.getPathContext();
082    
083                    String[] modulePaths = request.getParameterValues("m");
084    
085                    if ((modulePaths == null) || (modulePaths.length == 0)) {
086                            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
087    
088                            return;
089                    }
090    
091                    Set<String> modulePathsSet = new HashSet<String>(modulePaths.length);
092    
093                    for (String path : modulePaths) {
094                            modulePathsSet.add(path);
095                    }
096    
097                    modulePaths = modulePathsSet.toArray(new String[modulePathsSet.size()]);
098    
099                    Arrays.sort(modulePaths);
100    
101                    String modulePathsString = null;
102    
103                    byte[][] bytesArray = null;
104    
105                    if (!PropsValues.COMBO_CHECK_TIMESTAMP) {
106                            modulePathsString = Arrays.toString(modulePaths);
107    
108                            bytesArray = (byte[][])_bytesArrayPortalCache.get(
109                                    modulePathsString);
110                    }
111    
112                    String firstModulePath = modulePaths[0];
113    
114                    String extension = FileUtil.getExtension(firstModulePath);
115    
116                    if (bytesArray == null) {
117                            String p = ParamUtil.getString(request, "p");
118    
119                            String minifierType = ParamUtil.getString(request, "minifierType");
120    
121                            if (Validator.isNull(minifierType)) {
122                                    minifierType = "js";
123    
124                                    if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
125                                            minifierType = "css";
126                                    }
127                            }
128    
129                            if (!minifierType.equals("css") && !minifierType.equals("js")) {
130                                    minifierType = "js";
131                            }
132    
133                            int length = modulePaths.length;
134    
135                            bytesArray = new byte[length][];
136    
137                            for (String modulePath : modulePaths) {
138                                    if (!validateModuleExtension(modulePath)) {
139                                            response.setHeader(
140                                                    HttpHeaders.CACHE_CONTROL,
141                                                    HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
142                                            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
143    
144                                            return;
145                                    }
146    
147                                    byte[] bytes = new byte[0];
148    
149                                    if (Validator.isNotNull(modulePath)) {
150                                            modulePath = StringUtil.replaceFirst(
151                                                    p.concat(modulePath), contextPath, StringPool.BLANK);
152    
153                                            if (getFile(modulePath) == null) {
154                                                    response.setHeader(
155                                                            HttpHeaders.CACHE_CONTROL,
156                                                            HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
157                                                    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
158    
159                                                    return;
160                                            }
161    
162                                            bytes = getFileContent(
163                                                    request, response, modulePath, minifierType);
164                                    }
165    
166                                    bytesArray[--length] = bytes;
167                            }
168    
169                            if ((modulePathsString != null) &&
170                                    !PropsValues.COMBO_CHECK_TIMESTAMP) {
171    
172                                    _bytesArrayPortalCache.put(modulePathsString, bytesArray);
173                            }
174                    }
175    
176                    String contentType = ContentTypes.TEXT_JAVASCRIPT;
177    
178                    if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
179                            contentType = ContentTypes.TEXT_CSS;
180                    }
181    
182                    response.setContentType(contentType);
183    
184                    ServletResponseUtil.write(response, bytesArray);
185            }
186    
187            protected File getFile(String path) throws IOException {
188                    ServletContext servletContext = getServletContext();
189    
190                    String basePath = ServletContextUtil.getRealPath(
191                            servletContext, _JAVASCRIPT_DIR);
192    
193                    if (basePath == null) {
194                            return null;
195                    }
196    
197                    basePath = StringUtil.replace(
198                            basePath, CharPool.BACK_SLASH, CharPool.SLASH);
199    
200                    File baseDir = new File(basePath);
201    
202                    if (!baseDir.exists()) {
203                            return null;
204                    }
205    
206                    String filePath = ServletContextUtil.getRealPath(servletContext, path);
207    
208                    if (filePath == null) {
209                            return null;
210                    }
211    
212                    filePath = StringUtil.replace(
213                            filePath, CharPool.BACK_SLASH, CharPool.SLASH);
214    
215                    File file = new File(filePath);
216    
217                    if (!file.exists()) {
218                            return null;
219                    }
220    
221                    String baseCanonicalPath = baseDir.getCanonicalPath();
222                    String fileCanonicalPath = file.getCanonicalPath();
223    
224                    if (fileCanonicalPath.indexOf(baseCanonicalPath) == 0) {
225                            return file;
226                    }
227    
228                    return null;
229            }
230    
231            protected byte[] getFileContent(
232                            HttpServletRequest request, HttpServletResponse response,
233                            String path, String minifierType)
234                    throws IOException {
235    
236                    String fileContentKey = path.concat(StringPool.QUESTION).concat(
237                            minifierType);
238    
239                    FileContentBag fileContentBag =
240                            (FileContentBag)_fileContentBagPortalCache.get(fileContentKey);
241    
242                    if ((fileContentBag != null) && !PropsValues.COMBO_CHECK_TIMESTAMP) {
243                            return fileContentBag._fileContent;
244                    }
245    
246                    File file = getFile(path);
247    
248                    if ((fileContentBag != null) && PropsValues.COMBO_CHECK_TIMESTAMP) {
249                            long elapsedTime =
250                                    System.currentTimeMillis() - fileContentBag._lastModified;
251    
252                            if ((file != null) &&
253                                    (elapsedTime <= PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL) &&
254                                    (file.lastModified() == fileContentBag._lastModified)) {
255    
256                                    return fileContentBag._fileContent;
257                            }
258                            else {
259                                    _fileContentBagPortalCache.remove(fileContentKey);
260                            }
261                    }
262    
263                    if (file == null) {
264                            fileContentBag = _EMPTY_FILE_CONTENT_BAG;
265                    }
266                    else {
267                            String stringFileContent = FileUtil.read(file);
268    
269                            if (!StringUtil.endsWith(path, _CSS_MINIFIED_SUFFIX) &&
270                                    !StringUtil.endsWith(path, _JAVASCRIPT_MINIFIED_SUFFIX)) {
271    
272                                    if (minifierType.equals("css")) {
273                                            String cssRealPath = file.getAbsolutePath();
274    
275                                            try {
276                                                    stringFileContent = DynamicCSSUtil.parseSass(
277                                                            request, cssRealPath, stringFileContent);
278                                            }
279                                            catch (Exception e) {
280                                                    _log.error(
281                                                            "Unable to parse SASS on CSS " + cssRealPath, e);
282    
283                                                    if (_log.isDebugEnabled()) {
284                                                            _log.debug(stringFileContent);
285                                                    }
286    
287                                                    response.setHeader(
288                                                            HttpHeaders.CACHE_CONTROL,
289                                                            HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
290                                            }
291    
292                                            stringFileContent = MinifierUtil.minifyCss(
293                                                    stringFileContent);
294                                    }
295                                    else if (minifierType.equals("js")) {
296                                            stringFileContent = MinifierUtil.minifyJavaScript(
297                                                    stringFileContent);
298                                    }
299                            }
300    
301                            fileContentBag = new FileContentBag(
302                                    stringFileContent.getBytes(StringPool.UTF8),
303                                    file.lastModified());
304                    }
305    
306                    if (PropsValues.COMBO_CHECK_TIMESTAMP) {
307                            int timeToLive =
308                                    (int)(PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL / Time.SECOND);
309    
310                            _fileContentBagPortalCache.put(
311                                    fileContentKey, fileContentBag, timeToLive);
312                    }
313    
314                    return fileContentBag._fileContent;
315            }
316    
317            protected boolean validateModuleExtension(String moduleName)
318                    throws Exception {
319    
320                    boolean validModuleExtension = false;
321    
322                    String[] fileExtensions = PrefsPropsUtil.getStringArray(
323                            PropsKeys.COMBO_ALLOWED_FILE_EXTENSIONS, StringPool.COMMA);
324    
325                    for (int i = 0; i < fileExtensions.length; i++) {
326                            if (StringPool.STAR.equals(fileExtensions[i]) ||
327                                    StringUtil.endsWith(moduleName, fileExtensions[i])) {
328    
329                                    validModuleExtension = true;
330    
331                                    break;
332                            }
333                    }
334    
335                    return validModuleExtension;
336            }
337    
338            private static final String _CSS_EXTENSION = "css";
339    
340            private static final String _CSS_MINIFIED_SUFFIX = "-min.css";
341    
342            private static final FileContentBag _EMPTY_FILE_CONTENT_BAG =
343                    new FileContentBag(new byte[0], 0);
344    
345            private static final String _JAVASCRIPT_DIR = "html/js";
346    
347            private static final String _JAVASCRIPT_MINIFIED_SUFFIX = "-min.js";
348    
349            private static Log _log = LogFactoryUtil.getLog(ComboServlet.class);
350    
351            private PortalCache _bytesArrayPortalCache = SingleVMPoolUtil.getCache(
352                    ComboServlet.class.getName());
353            private PortalCache _fileContentBagPortalCache = SingleVMPoolUtil.getCache(
354                    FileContentBag.class.getName());
355    
356            private static class FileContentBag implements Serializable {
357    
358                    public FileContentBag(byte[] fileContent, long lastModifiedTime) {
359                            _fileContent = fileContent;
360                            _lastModified = lastModifiedTime;
361                    }
362    
363                    private byte[] _fileContent;
364                    private long _lastModified;
365    
366            }
367    
368    }