1   /**
2    * Copyright (c) 2000-2009 Liferay, Inc. All rights reserved.
3    *
4    * Permission is hereby granted, free of charge, to any person obtaining a copy
5    * of this software and associated documentation files (the "Software"), to deal
6    * in the Software without restriction, including without limitation the rights
7    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    * copies of the Software, and to permit persons to whom the Software is
9    * furnished to do so, subject to the following conditions:
10   *
11   * The above copyright notice and this permission notice shall be included in
12   * all copies or substantial portions of the Software.
13   *
14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20   * SOFTWARE.
21   */
22  
23  package com.liferay.portal.servlet.filters.strip;
24  
25  import com.liferay.portal.kernel.log.Log;
26  import com.liferay.portal.kernel.log.LogFactoryUtil;
27  import com.liferay.portal.kernel.portlet.LiferayWindowState;
28  import com.liferay.portal.kernel.util.CharPool;
29  import com.liferay.portal.kernel.util.GetterUtil;
30  import com.liferay.portal.kernel.util.HttpUtil;
31  import com.liferay.portal.kernel.util.JavaConstants;
32  import com.liferay.portal.kernel.util.ParamUtil;
33  import com.liferay.portal.kernel.util.Validator;
34  import com.liferay.portal.servlet.filters.BasePortalFilter;
35  import com.liferay.portal.util.MinifierUtil;
36  import com.liferay.util.servlet.ServletResponseUtil;
37  
38  import java.io.IOException;
39  
40  import javax.servlet.FilterChain;
41  import javax.servlet.ServletException;
42  import javax.servlet.http.HttpServletRequest;
43  import javax.servlet.http.HttpServletResponse;
44  
45  /**
46   * <a href="StripFilter.java.html"><b><i>View Source</i></b></a>
47   *
48   * @author Brian Wing Shun Chan
49   * @author Raymond Augé
50   *
51   */
52  public class StripFilter extends BasePortalFilter {
53  
54      public static final String SKIP_FILTER =
55          StripFilter.class.getName() + "SKIP_FILTER";
56  
57      protected boolean hasMarker(byte[] oldByteArray, int pos, char[] marker) {
58          if ((pos + marker.length) >= oldByteArray.length) {
59              return false;
60          }
61  
62          for (int i = 0; i < marker.length; i++) {
63              char c = marker[i];
64  
65              char oldC = (char)oldByteArray[pos + i + 1];
66  
67              if ((c != oldC) &&
68                  (Character.toUpperCase(c) != oldC)) {
69  
70                  return false;
71              }
72          }
73  
74          return true;
75      }
76  
77      protected boolean isAlreadyFiltered(HttpServletRequest request) {
78          if (request.getAttribute(SKIP_FILTER) != null) {
79              return true;
80          }
81          else {
82              return false;
83          }
84      }
85  
86      protected boolean isInclude(HttpServletRequest request) {
87          String uri = (String)request.getAttribute(
88              JavaConstants.JAVAX_SERVLET_INCLUDE_REQUEST_URI);
89  
90          if (uri == null) {
91              return false;
92          }
93          else {
94              return true;
95          }
96      }
97  
98      protected boolean isStrip(HttpServletRequest request) {
99          if (!ParamUtil.getBoolean(request, _STRIP, true)) {
100             return false;
101         }
102         else {
103 
104             // Modifying binary content through a servlet filter under certain
105             // conditions is bad on performance the user will not start
106             // downloading the content until the entire content is modified.
107 
108             String lifecycle = ParamUtil.getString(request, "p_p_lifecycle");
109 
110             if ((lifecycle.equals("1") &&
111                  LiferayWindowState.isExclusive(request)) ||
112                 lifecycle.equals("2")) {
113 
114                 return false;
115             }
116             else {
117                 return true;
118             }
119         }
120     }
121 
122     protected void processFilter(
123             HttpServletRequest request, HttpServletResponse response,
124             FilterChain filterChain)
125         throws IOException, ServletException {
126 
127         String completeURL = HttpUtil.getCompleteURL(request);
128 
129         if (isStrip(request) && !isInclude(request) &&
130             !isAlreadyFiltered(request)) {
131 
132             if (_log.isDebugEnabled()) {
133                 _log.debug("Stripping " + completeURL);
134             }
135 
136             request.setAttribute(SKIP_FILTER, Boolean.TRUE);
137 
138             StripResponse stripResponse = new StripResponse(response);
139 
140             processFilter(
141                 StripFilter.class, request, stripResponse, filterChain);
142 
143             String contentType = GetterUtil.getString(
144                 stripResponse.getContentType()).toLowerCase();
145 
146             byte[] oldByteArray = stripResponse.getData();
147 
148             if ((oldByteArray != null) && (oldByteArray.length > 0)) {
149                 byte[] newByteArray = null;
150                 int newByteArrayPos = 0;
151 
152                 if (_log.isDebugEnabled()) {
153                     _log.debug("Stripping content of type " + contentType);
154                 }
155 
156                 if (contentType.indexOf("text/") != -1) {
157                     Object[] value = strip(oldByteArray);
158 
159                     newByteArray = (byte[])value[0];
160                     newByteArrayPos = (Integer)value[1];
161                 }
162                 else {
163                     newByteArray = oldByteArray;
164                     newByteArrayPos = oldByteArray.length;
165                 }
166 
167                 ServletResponseUtil.write(
168                     response, newByteArray, newByteArrayPos);
169             }
170         }
171         else {
172             if (_log.isDebugEnabled()) {
173                 _log.debug("Not stripping " + completeURL);
174             }
175 
176             processFilter(StripFilter.class, request, response, filterChain);
177         }
178     }
179 
180     protected Object[] strip(byte[] oldByteArray) throws IOException {
181         byte[] newByteArray = new byte[oldByteArray.length];
182         int newByteArrayPos = 0;
183 
184         int state = _STATE_NORMAL;
185 
186         boolean removeStartingWhitespace = true;
187 
188         StringBuilder scriptSB = new StringBuilder();
189         StringBuilder styleSB = new StringBuilder();
190 
191         for (int i = 0; i < oldByteArray.length; i++) {
192             byte b = oldByteArray[i];
193 
194             char c = (char)b;
195 
196             if (c == CharPool.LESS_THAN) {
197                 if (state == _STATE_NORMAL) {
198                     if (hasMarker(oldByteArray, i, _MARKER_PRE_OPEN) ||
199                         hasMarker(oldByteArray, i, _MARKER_TEXTAREA_OPEN)) {
200 
201                         state = _STATE_IGNORE;
202                     }
203                     else if (hasMarker(oldByteArray, i, _MARKER_DIV_CLOSE) ||
204                              hasMarker(oldByteArray, i, _MARKER_FORM_CLOSE) ||
205                              hasMarker(oldByteArray, i, _MARKER_LI_CLOSE) ||
206                              hasMarker(oldByteArray, i, _MARKER_SCRIPT_CLOSE) ||
207                              hasMarker(oldByteArray, i, _MARKER_STYLE_CLOSE) ||
208                              hasMarker(oldByteArray, i, _MARKER_TABLE_CLOSE) ||
209                              hasMarker(oldByteArray, i, _MARKER_TD_CLOSE) ||
210                              hasMarker(oldByteArray, i, _MARKER_TD_OPEN) ||
211                              hasMarker(oldByteArray, i, _MARKER_TR_CLOSE) ||
212                              hasMarker(oldByteArray, i, _MARKER_TR_OPEN) ||
213                              hasMarker(oldByteArray, i, _MARKER_UL_CLOSE)) {
214 
215                         state = _STATE_FOUND_ELEMENT;
216                     }
217                     else if (hasMarker(oldByteArray, i, _MARKER_SCRIPT_OPEN)) {
218                         state = _STATE_MINIFY_SCRIPT;
219                     }
220                     else if (hasMarker(oldByteArray, i, _MARKER_STYLE_OPEN)) {
221                         state = _STATE_MINIFY_STYLE;
222                     }
223                 }
224                 else if (state == _STATE_IGNORE) {
225                     if (hasMarker(oldByteArray, i, _MARKER_PRE_CLOSE) ||
226                         hasMarker(oldByteArray, i, _MARKER_TEXTAREA_CLOSE)) {
227 
228                         state = _STATE_NORMAL;
229                     }
230                 }
231                 else if (state == _STATE_MINIFY_SCRIPT) {
232                     if (hasMarker(oldByteArray, i, _MARKER_SCRIPT_CLOSE)) {
233                         state = _STATE_NORMAL;
234 
235                         String scriptContent = scriptSB.toString();
236 
237                         scriptSB = new StringBuilder();
238 
239                         scriptContent = scriptContent.substring(
240                             _SCRIPT_TYPE_JAVASCRIPT.length()).trim();
241 
242                         if (Validator.isNull(scriptContent)) {
243                             i += _MARKER_SCRIPT_CLOSE.length;
244 
245                             continue;
246                         }
247 
248                         scriptContent = MinifierUtil.minifyJavaScript(
249                             scriptContent);
250 
251                         if (Validator.isNull(scriptContent)) {
252                             i += _MARKER_SCRIPT_CLOSE.length;
253 
254                             continue;
255                         }
256 
257                         scriptContent = _SCRIPT_TYPE_JAVASCRIPT + scriptContent;
258 
259                         for (byte curByte : scriptContent.getBytes()) {
260                             newByteArray[newByteArrayPos++] = curByte;
261                         }
262 
263                         state = _STATE_FOUND_ELEMENT;
264                     }
265                 }
266                 else if (state == _STATE_MINIFY_STYLE) {
267                     if (hasMarker(oldByteArray, i, _MARKER_STYLE_CLOSE)) {
268                         state = _STATE_NORMAL;
269 
270                         String styleContent = styleSB.toString();
271 
272                         styleSB = new StringBuilder();
273 
274                         styleContent = styleContent.substring(
275                             _STYLE_TYPE_CSS.length()).trim();
276 
277                         if (Validator.isNull(styleContent)) {
278                             i += _MARKER_STYLE_CLOSE.length;
279 
280                             continue;
281                         }
282 
283                         styleContent = MinifierUtil.minifyCss(styleContent);
284 
285                         if (Validator.isNull(styleContent)) {
286                             i += _MARKER_STYLE_CLOSE.length;
287 
288                             continue;
289                         }
290 
291                         styleContent = _STYLE_TYPE_CSS + styleContent;
292 
293                         for (byte curByte : styleContent.getBytes()) {
294                             newByteArray[newByteArrayPos++] = curByte;
295                         }
296 
297                         state = _STATE_FOUND_ELEMENT;
298                     }
299                 }
300             }
301             else if (c == CharPool.GREATER_THAN) {
302                 if (state == _STATE_FOUND_ELEMENT) {
303                     state = _STATE_NORMAL;
304 
305                     newByteArray[newByteArrayPos++] = b;
306 
307                     while ((i + 1) < oldByteArray.length) {
308                         char nextChar = (char)oldByteArray[i + 1];
309 
310                         if (Validator.isWhitespace(nextChar)) {
311                             i++;
312                         }
313                         else {
314                             break;
315                         }
316                     }
317 
318                     continue;
319                 }
320             }
321 
322             if (state == _STATE_NORMAL) {
323                 if ((i + 1) < oldByteArray.length) {
324                     if (removeStartingWhitespace) {
325                         if (Validator.isWhitespace(c)) {
326                             continue;
327                         }
328                         else {
329                             removeStartingWhitespace = false;
330                         }
331                     }
332 
333                     if ((c == CharPool.NEW_LINE) ||
334                         (c == CharPool.RETURN) ||
335                         (c == CharPool.TAB)) {
336 
337                         char nextChar = (char)oldByteArray[i + 1];
338 
339                         if ((nextChar == CharPool.NEW_LINE) ||
340                             (nextChar == CharPool.RETURN) ||
341                             (nextChar == CharPool.TAB)) {
342 
343                             continue;
344                         }
345                     }
346                 }
347             }
348 
349             if (state == _STATE_MINIFY_SCRIPT) {
350                 scriptSB.append(c);
351             }
352             else if (state == _STATE_MINIFY_STYLE) {
353                 styleSB.append(c);
354             }
355             else {
356                 newByteArray[newByteArrayPos++] = b;
357             }
358         }
359 
360         if (newByteArrayPos > 1) {
361             for (int i = newByteArrayPos - 1; i > 0; i--) {
362                 byte b = newByteArray[i];
363 
364                 char c = (char)b;
365 
366                 if (Validator.isWhitespace(c)) {
367                     newByteArrayPos--;
368                 }
369                 else {
370                     break;
371                 }
372             }
373         }
374 
375         if (state == _STATE_MINIFY_SCRIPT) {
376             _log.error("Missing </script>");
377         }
378         else if (state == _STATE_MINIFY_STYLE) {
379             _log.error("Missing </style>");
380         }
381 
382         return new Object[] {newByteArray, newByteArrayPos};
383     }
384 
385     private static final char[] _MARKER_DIV_CLOSE = "/div>".toCharArray();
386 
387     private static final char[] _MARKER_FORM_CLOSE = "/form>".toCharArray();
388 
389     private static final char[] _MARKER_LI_CLOSE = "/li>".toCharArray();
390 
391     private static final char[] _MARKER_PRE_CLOSE = "/pre>".toCharArray();
392 
393     private static final char[] _MARKER_PRE_OPEN = "pre>".toCharArray();
394 
395     private static final char[] _MARKER_SCRIPT_OPEN =
396         "script type=\"text/javascript\">".toCharArray();
397 
398     private static final char[] _MARKER_SCRIPT_CLOSE = "/script>".toCharArray();
399 
400     private static final char[] _MARKER_STYLE_OPEN =
401         "style type=\"text/css\">".toCharArray();
402 
403     private static final char[] _MARKER_STYLE_CLOSE = "/style>".toCharArray();
404 
405     private static final char[] _MARKER_TABLE_CLOSE = "/table>".toCharArray();
406 
407     private static final char[] _MARKER_TD_CLOSE = "/td>".toCharArray();
408 
409     private static final char[] _MARKER_TD_OPEN = "td>".toCharArray();
410 
411     private static final char[] _MARKER_TR_CLOSE = "/tr>".toCharArray();
412 
413     private static final char[] _MARKER_TR_OPEN = "tr>".toCharArray();
414 
415     private static final char[] _MARKER_TEXTAREA_CLOSE =
416         "/textarea>".toCharArray();
417 
418     private static final char[] _MARKER_TEXTAREA_OPEN =
419         "textarea>".toCharArray();
420 
421     private static final char[] _MARKER_UL_CLOSE = "/ul>".toCharArray();
422 
423     private static final String _SCRIPT_TYPE_JAVASCRIPT =
424         "<script type=\"text/javascript\">";
425 
426     private static final int _STATE_FOUND_ELEMENT = 3;
427 
428     private static final int _STATE_IGNORE = 1;
429 
430     private static final int _STATE_MINIFY_SCRIPT = 4;
431 
432     private static final int _STATE_MINIFY_STYLE = 5;
433 
434     private static final int _STATE_NORMAL = 0;
435 
436     private static final String _STYLE_TYPE_CSS = "<style type=\"text/css\">";
437 
438     private static final String _STRIP = "strip";
439 
440     private static Log _log = LogFactoryUtil.getLog(StripFilter.class);
441 
442 }