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.ByteArrayMaker;
29  import com.liferay.portal.kernel.util.CharPool;
30  import com.liferay.portal.kernel.util.GetterUtil;
31  import com.liferay.portal.kernel.util.HttpUtil;
32  import com.liferay.portal.kernel.util.JavaConstants;
33  import com.liferay.portal.kernel.util.ParamUtil;
34  import com.liferay.portal.kernel.util.StringPool;
35  import com.liferay.portal.kernel.util.Validator;
36  import com.liferay.portal.servlet.filters.BasePortalFilter;
37  import com.liferay.portal.util.MinifierUtil;
38  import com.liferay.util.servlet.ServletResponseUtil;
39  
40  import java.io.IOException;
41  
42  import javax.servlet.FilterChain;
43  import javax.servlet.ServletException;
44  import javax.servlet.http.HttpServletRequest;
45  import javax.servlet.http.HttpServletResponse;
46  
47  /**
48   * <a href="StripFilter.java.html"><b><i>View Source</i></b></a>
49   *
50   * @author Brian Wing Shun Chan
51   * @author Raymond Augé
52   *
53   */
54  public class StripFilter extends BasePortalFilter {
55  
56      public static final String SKIP_FILTER =
57          StripFilter.class.getName() + "SKIP_FILTER";
58  
59      protected boolean hasMarker(byte[] oldByteArray, int pos, char[] marker) {
60          if ((pos + marker.length) >= oldByteArray.length) {
61              return false;
62          }
63  
64          for (int i = 0; i < marker.length; i++) {
65              char c = marker[i];
66  
67              char oldC = (char)oldByteArray[pos + i + 1];
68  
69              if ((c != oldC) &&
70                  (Character.toUpperCase(c) != oldC)) {
71  
72                  return false;
73              }
74          }
75  
76          return true;
77      }
78  
79      protected boolean isAlreadyFiltered(HttpServletRequest request) {
80          if (request.getAttribute(SKIP_FILTER) != null) {
81              return true;
82          }
83          else {
84              return false;
85          }
86      }
87  
88      protected boolean isInclude(HttpServletRequest request) {
89          String uri = (String)request.getAttribute(
90              JavaConstants.JAVAX_SERVLET_INCLUDE_REQUEST_URI);
91  
92          if (uri == null) {
93              return false;
94          }
95          else {
96              return true;
97          }
98      }
99  
100     protected boolean isStrip(HttpServletRequest request) {
101         if (!ParamUtil.getBoolean(request, _STRIP, true)) {
102             return false;
103         }
104         else {
105 
106             // Modifying binary content through a servlet filter under certain
107             // conditions is bad on performance the user will not start
108             // downloading the content until the entire content is modified.
109 
110             String lifecycle = ParamUtil.getString(request, "p_p_lifecycle");
111 
112             if ((lifecycle.equals("1") &&
113                  LiferayWindowState.isExclusive(request)) ||
114                 lifecycle.equals("2")) {
115 
116                 return false;
117             }
118             else {
119                 return true;
120             }
121         }
122     }
123 
124     protected void processFilter(
125             HttpServletRequest request, HttpServletResponse response,
126             FilterChain filterChain)
127         throws IOException, ServletException {
128 
129         String completeURL = HttpUtil.getCompleteURL(request);
130 
131         if (isStrip(request) && !isInclude(request) &&
132             !isAlreadyFiltered(request)) {
133 
134             if (_log.isDebugEnabled()) {
135                 _log.debug("Stripping " + completeURL);
136             }
137 
138             request.setAttribute(SKIP_FILTER, Boolean.TRUE);
139 
140             StripResponse stripResponse = new StripResponse(response);
141 
142             processFilter(
143                 StripFilter.class, request, stripResponse, filterChain);
144 
145             String contentType = GetterUtil.getString(
146                 stripResponse.getContentType()).toLowerCase();
147 
148             byte[] oldByteArray = stripResponse.getData();
149 
150             if ((oldByteArray != null) && (oldByteArray.length > 0)) {
151                 byte[] newByteArray = null;
152                 int newByteArrayPos = 0;
153 
154                 if (_log.isDebugEnabled()) {
155                     _log.debug("Stripping content of type " + contentType);
156                 }
157 
158                 if (contentType.indexOf("text/") != -1) {
159                     Object[] value = strip(oldByteArray);
160 
161                     newByteArray = (byte[])value[0];
162                     newByteArrayPos = (Integer)value[1];
163                 }
164                 else {
165                     newByteArray = oldByteArray;
166                     newByteArrayPos = oldByteArray.length;
167                 }
168 
169                 ServletResponseUtil.write(
170                     response, newByteArray, newByteArrayPos);
171             }
172         }
173         else {
174             if (_log.isDebugEnabled()) {
175                 _log.debug("Not stripping " + completeURL);
176             }
177 
178             processFilter(StripFilter.class, request, response, filterChain);
179         }
180     }
181 
182     protected Object[] strip(byte[] oldByteArray) throws IOException {
183         byte[] newByteArray = new byte[oldByteArray.length];
184         int newByteArrayPos = 0;
185 
186         int state = _STATE_NORMAL;
187 
188         boolean removeStartingWhitespace = true;
189 
190         ByteArrayMaker scriptBytes = new ByteArrayMaker();
191         ByteArrayMaker styleBytes = new ByteArrayMaker();
192 
193         for (int i = 0; i < oldByteArray.length; i++) {
194             byte b = oldByteArray[i];
195 
196             char c = (char)b;
197 
198             if (c == CharPool.LESS_THAN) {
199                 if (state == _STATE_NORMAL) {
200                     if (hasMarker(oldByteArray, i, _MARKER_PRE_OPEN) ||
201                         hasMarker(oldByteArray, i, _MARKER_TEXTAREA_OPEN)) {
202 
203                         state = _STATE_IGNORE;
204                     }
205                     else if (hasMarker(oldByteArray, i, _MARKER_DIV_CLOSE) ||
206                              hasMarker(oldByteArray, i, _MARKER_FORM_CLOSE) ||
207                              hasMarker(oldByteArray, i, _MARKER_LI_CLOSE) ||
208                              hasMarker(oldByteArray, i, _MARKER_SCRIPT_CLOSE) ||
209                              hasMarker(oldByteArray, i, _MARKER_STYLE_CLOSE) ||
210                              hasMarker(oldByteArray, i, _MARKER_TABLE_CLOSE) ||
211                              hasMarker(oldByteArray, i, _MARKER_TD_CLOSE) ||
212                              hasMarker(oldByteArray, i, _MARKER_TD_OPEN) ||
213                              hasMarker(oldByteArray, i, _MARKER_TR_CLOSE) ||
214                              hasMarker(oldByteArray, i, _MARKER_TR_OPEN) ||
215                              hasMarker(oldByteArray, i, _MARKER_UL_CLOSE)) {
216 
217                         state = _STATE_FOUND_ELEMENT;
218                     }
219                     else if (hasMarker(
220                                 oldByteArray, i, _MARKER_JAVASCRIPT_OPEN) ||
221                              hasMarker(oldByteArray, i, _MARKER_SCRIPT_OPEN)) {
222 
223                         state = _STATE_MINIFY_SCRIPT;
224                     }
225                     else if (hasMarker(oldByteArray, i, _MARKER_STYLE_OPEN)) {
226                         state = _STATE_MINIFY_STYLE;
227                     }
228                 }
229                 else if (state == _STATE_IGNORE) {
230                     if (hasMarker(oldByteArray, i, _MARKER_PRE_CLOSE) ||
231                         hasMarker(oldByteArray, i, _MARKER_TEXTAREA_CLOSE)) {
232 
233                         state = _STATE_NORMAL;
234                     }
235                 }
236                 else if (state == _STATE_MINIFY_SCRIPT) {
237                     if (hasMarker(oldByteArray, i, _MARKER_SCRIPT_CLOSE)) {
238                         state = _STATE_NORMAL;
239 
240                         String scriptContent = scriptBytes.toString(
241                             StringPool.UTF8);
242 
243                         scriptBytes = new ByteArrayMaker();
244 
245                         int pos = scriptContent.indexOf(CharPool.GREATER_THAN);
246 
247                         scriptContent = scriptContent.substring(pos + 1).trim();
248 
249                         if (Validator.isNull(scriptContent)) {
250                             i += _MARKER_SCRIPT_CLOSE.length;
251 
252                             continue;
253                         }
254 
255                         scriptContent = MinifierUtil.minifyJavaScript(
256                             scriptContent);
257 
258                         scriptContent =
259                             _CDATA_OPEN + scriptContent + _CDATA_CLOSE;
260 
261                         if (Validator.isNull(scriptContent)) {
262                             i += _MARKER_SCRIPT_CLOSE.length;
263 
264                             continue;
265                         }
266 
267                         scriptContent = _SCRIPT_TYPE_JAVASCRIPT + scriptContent;
268 
269                         byte[] scriptContentBytes = scriptContent.getBytes(
270                             StringPool.UTF8);
271 
272                         for (byte curByte : scriptContentBytes) {
273                             newByteArray[newByteArrayPos++] = curByte;
274                         }
275 
276                         state = _STATE_FOUND_ELEMENT;
277                     }
278                 }
279                 else if (state == _STATE_MINIFY_STYLE) {
280                     if (hasMarker(oldByteArray, i, _MARKER_STYLE_CLOSE)) {
281                         state = _STATE_NORMAL;
282 
283                         String styleContent = styleBytes.toString(
284                             StringPool.UTF8);
285 
286                         styleBytes = new ByteArrayMaker();
287 
288                         styleContent = styleContent.substring(
289                             _STYLE_TYPE_CSS.length()).trim();
290 
291                         if (Validator.isNull(styleContent)) {
292                             i += _MARKER_STYLE_CLOSE.length;
293 
294                             continue;
295                         }
296 
297                         styleContent = MinifierUtil.minifyCss(styleContent);
298 
299                         if (Validator.isNull(styleContent)) {
300                             i += _MARKER_STYLE_CLOSE.length;
301 
302                             continue;
303                         }
304 
305                         styleContent = _STYLE_TYPE_CSS + styleContent;
306 
307                         byte[] styleContentBytes = styleContent.getBytes(
308                             StringPool.UTF8);
309 
310                         for (byte curByte : styleContentBytes) {
311                             newByteArray[newByteArrayPos++] = curByte;
312                         }
313 
314                         state = _STATE_FOUND_ELEMENT;
315                     }
316                 }
317             }
318             else if (c == CharPool.GREATER_THAN) {
319                 if (state == _STATE_FOUND_ELEMENT) {
320                     state = _STATE_NORMAL;
321 
322                     newByteArray[newByteArrayPos++] = b;
323 
324                     while ((i + 1) < oldByteArray.length) {
325                         char nextChar = (char)oldByteArray[i + 1];
326 
327                         if (Validator.isWhitespace(nextChar)) {
328                             i++;
329                         }
330                         else {
331                             break;
332                         }
333                     }
334 
335                     continue;
336                 }
337             }
338 
339             if (state == _STATE_NORMAL) {
340                 if ((i + 1) < oldByteArray.length) {
341                     if (removeStartingWhitespace) {
342                         if (Validator.isWhitespace(c)) {
343                             continue;
344                         }
345                         else {
346                             removeStartingWhitespace = false;
347                         }
348                     }
349 
350                     if ((c == CharPool.NEW_LINE) ||
351                         (c == CharPool.RETURN) ||
352                         (c == CharPool.TAB)) {
353 
354                         char nextChar = (char)oldByteArray[i + 1];
355 
356                         if ((nextChar == CharPool.NEW_LINE) ||
357                             (nextChar == CharPool.RETURN) ||
358                             (nextChar == CharPool.TAB)) {
359 
360                             continue;
361                         }
362                     }
363                 }
364             }
365 
366             if (state == _STATE_MINIFY_SCRIPT) {
367                 scriptBytes.write(b);
368             }
369             else if (state == _STATE_MINIFY_STYLE) {
370                 styleBytes.write(b);
371             }
372             else {
373                 newByteArray[newByteArrayPos++] = b;
374             }
375         }
376 
377         if (newByteArrayPos > 1) {
378             for (int i = newByteArrayPos - 1; i > 0; i--) {
379                 byte b = newByteArray[i];
380 
381                 char c = (char)b;
382 
383                 if (Validator.isWhitespace(c)) {
384                     newByteArrayPos--;
385                 }
386                 else {
387                     break;
388                 }
389             }
390         }
391 
392         if (state == _STATE_MINIFY_SCRIPT) {
393             _log.error("Missing </script>");
394         }
395         else if (state == _STATE_MINIFY_STYLE) {
396             _log.error("Missing </style>");
397         }
398 
399         return new Object[] {newByteArray, newByteArrayPos};
400     }
401 
402     private static final String _CDATA_CLOSE = "/*]]>*/";
403 
404     private static final String _CDATA_OPEN = "/*<![CDATA[*/";
405 
406     private static final char[] _MARKER_DIV_CLOSE = "/div>".toCharArray();
407 
408     private static final char[] _MARKER_FORM_CLOSE = "/form>".toCharArray();
409 
410     private static final char[] _MARKER_JAVASCRIPT_OPEN =
411         "script type=\"text/javascript\">".toCharArray();
412 
413     private static final char[] _MARKER_LI_CLOSE = "/li>".toCharArray();
414 
415     private static final char[] _MARKER_PRE_CLOSE = "/pre>".toCharArray();
416 
417     private static final char[] _MARKER_PRE_OPEN = "pre>".toCharArray();
418 
419     private static final char[] _MARKER_SCRIPT_OPEN = "script>".toCharArray();
420 
421     private static final char[] _MARKER_SCRIPT_CLOSE = "/script>".toCharArray();
422 
423     private static final char[] _MARKER_STYLE_OPEN =
424         "style type=\"text/css\">".toCharArray();
425 
426     private static final char[] _MARKER_STYLE_CLOSE = "/style>".toCharArray();
427 
428     private static final char[] _MARKER_TABLE_CLOSE = "/table>".toCharArray();
429 
430     private static final char[] _MARKER_TD_CLOSE = "/td>".toCharArray();
431 
432     private static final char[] _MARKER_TD_OPEN = "td>".toCharArray();
433 
434     private static final char[] _MARKER_TR_CLOSE = "/tr>".toCharArray();
435 
436     private static final char[] _MARKER_TR_OPEN = "tr>".toCharArray();
437 
438     private static final char[] _MARKER_TEXTAREA_CLOSE =
439         "/textarea>".toCharArray();
440 
441     private static final char[] _MARKER_TEXTAREA_OPEN =
442         "textarea ".toCharArray();
443 
444     private static final char[] _MARKER_UL_CLOSE = "/ul>".toCharArray();
445 
446     private static final String _SCRIPT_TYPE_JAVASCRIPT =
447         "<script type=\"text/javascript\">";
448 
449     private static final int _STATE_FOUND_ELEMENT = 3;
450 
451     private static final int _STATE_IGNORE = 1;
452 
453     private static final int _STATE_MINIFY_SCRIPT = 4;
454 
455     private static final int _STATE_MINIFY_STYLE = 5;
456 
457     private static final int _STATE_NORMAL = 0;
458 
459     private static final String _STYLE_TYPE_CSS = "<style type=\"text/css\">";
460 
461     private static final String _STRIP = "strip";
462 
463     private static Log _log = LogFactoryUtil.getLog(StripFilter.class);
464 
465 }