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(oldByteArray, i, _MARKER_SCRIPT_OPEN)) {
220                         state = _STATE_MINIFY_SCRIPT;
221                     }
222                     else if (hasMarker(oldByteArray, i, _MARKER_STYLE_OPEN)) {
223                         state = _STATE_MINIFY_STYLE;
224                     }
225                 }
226                 else if (state == _STATE_IGNORE) {
227                     if (hasMarker(oldByteArray, i, _MARKER_PRE_CLOSE) ||
228                         hasMarker(oldByteArray, i, _MARKER_TEXTAREA_CLOSE)) {
229 
230                         state = _STATE_NORMAL;
231                     }
232                 }
233                 else if (state == _STATE_MINIFY_SCRIPT) {
234                     if (hasMarker(oldByteArray, i, _MARKER_SCRIPT_CLOSE)) {
235                         state = _STATE_NORMAL;
236 
237                         String scriptContent = scriptBytes.toString(
238                             StringPool.UTF8);
239 
240                         scriptBytes = new ByteArrayMaker();
241 
242                         scriptContent = scriptContent.substring(
243                             _SCRIPT_TYPE_JAVASCRIPT.length()).trim();
244 
245                         if (Validator.isNull(scriptContent)) {
246                             i += _MARKER_SCRIPT_CLOSE.length;
247 
248                             continue;
249                         }
250 
251                         scriptContent = MinifierUtil.minifyJavaScript(
252                             scriptContent);
253 
254                         if (Validator.isNull(scriptContent)) {
255                             i += _MARKER_SCRIPT_CLOSE.length;
256 
257                             continue;
258                         }
259 
260                         scriptContent = _SCRIPT_TYPE_JAVASCRIPT + scriptContent;
261 
262                         byte[] scriptContentBytes = scriptContent.getBytes(
263                             StringPool.UTF8);
264 
265                         for (byte curByte : scriptContentBytes) {
266                             newByteArray[newByteArrayPos++] = curByte;
267                         }
268 
269                         state = _STATE_FOUND_ELEMENT;
270                     }
271                 }
272                 else if (state == _STATE_MINIFY_STYLE) {
273                     if (hasMarker(oldByteArray, i, _MARKER_STYLE_CLOSE)) {
274                         state = _STATE_NORMAL;
275 
276                         String styleContent = styleBytes.toString(
277                             StringPool.UTF8);
278 
279                         styleBytes = new ByteArrayMaker();
280 
281                         styleContent = styleContent.substring(
282                             _STYLE_TYPE_CSS.length()).trim();
283 
284                         if (Validator.isNull(styleContent)) {
285                             i += _MARKER_STYLE_CLOSE.length;
286 
287                             continue;
288                         }
289 
290                         styleContent = MinifierUtil.minifyCss(styleContent);
291 
292                         if (Validator.isNull(styleContent)) {
293                             i += _MARKER_STYLE_CLOSE.length;
294 
295                             continue;
296                         }
297 
298                         styleContent = _STYLE_TYPE_CSS + styleContent;
299 
300                         byte[] styleContentBytes = styleContent.getBytes(
301                             StringPool.UTF8);
302 
303                         for (byte curByte : styleContentBytes) {
304                             newByteArray[newByteArrayPos++] = curByte;
305                         }
306 
307                         state = _STATE_FOUND_ELEMENT;
308                     }
309                 }
310             }
311             else if (c == CharPool.GREATER_THAN) {
312                 if (state == _STATE_FOUND_ELEMENT) {
313                     state = _STATE_NORMAL;
314 
315                     newByteArray[newByteArrayPos++] = b;
316 
317                     while ((i + 1) < oldByteArray.length) {
318                         char nextChar = (char)oldByteArray[i + 1];
319 
320                         if (Validator.isWhitespace(nextChar)) {
321                             i++;
322                         }
323                         else {
324                             break;
325                         }
326                     }
327 
328                     continue;
329                 }
330             }
331 
332             if (state == _STATE_NORMAL) {
333                 if ((i + 1) < oldByteArray.length) {
334                     if (removeStartingWhitespace) {
335                         if (Validator.isWhitespace(c)) {
336                             continue;
337                         }
338                         else {
339                             removeStartingWhitespace = false;
340                         }
341                     }
342 
343                     if ((c == CharPool.NEW_LINE) ||
344                         (c == CharPool.RETURN) ||
345                         (c == CharPool.TAB)) {
346 
347                         char nextChar = (char)oldByteArray[i + 1];
348 
349                         if ((nextChar == CharPool.NEW_LINE) ||
350                             (nextChar == CharPool.RETURN) ||
351                             (nextChar == CharPool.TAB)) {
352 
353                             continue;
354                         }
355                     }
356                 }
357             }
358 
359             if (state == _STATE_MINIFY_SCRIPT) {
360                 scriptBytes.write(b);
361             }
362             else if (state == _STATE_MINIFY_STYLE) {
363                 styleBytes.write(b);
364             }
365             else {
366                 newByteArray[newByteArrayPos++] = b;
367             }
368         }
369 
370         if (newByteArrayPos > 1) {
371             for (int i = newByteArrayPos - 1; i > 0; i--) {
372                 byte b = newByteArray[i];
373 
374                 char c = (char)b;
375 
376                 if (Validator.isWhitespace(c)) {
377                     newByteArrayPos--;
378                 }
379                 else {
380                     break;
381                 }
382             }
383         }
384 
385         if (state == _STATE_MINIFY_SCRIPT) {
386             _log.error("Missing </script>");
387         }
388         else if (state == _STATE_MINIFY_STYLE) {
389             _log.error("Missing </style>");
390         }
391 
392         return new Object[] {newByteArray, newByteArrayPos};
393     }
394 
395     private static final char[] _MARKER_DIV_CLOSE = "/div>".toCharArray();
396 
397     private static final char[] _MARKER_FORM_CLOSE = "/form>".toCharArray();
398 
399     private static final char[] _MARKER_LI_CLOSE = "/li>".toCharArray();
400 
401     private static final char[] _MARKER_PRE_CLOSE = "/pre>".toCharArray();
402 
403     private static final char[] _MARKER_PRE_OPEN = "pre>".toCharArray();
404 
405     private static final char[] _MARKER_SCRIPT_OPEN =
406         "script type=\"text/javascript\">".toCharArray();
407 
408     private static final char[] _MARKER_SCRIPT_CLOSE = "/script>".toCharArray();
409 
410     private static final char[] _MARKER_STYLE_OPEN =
411         "style type=\"text/css\">".toCharArray();
412 
413     private static final char[] _MARKER_STYLE_CLOSE = "/style>".toCharArray();
414 
415     private static final char[] _MARKER_TABLE_CLOSE = "/table>".toCharArray();
416 
417     private static final char[] _MARKER_TD_CLOSE = "/td>".toCharArray();
418 
419     private static final char[] _MARKER_TD_OPEN = "td>".toCharArray();
420 
421     private static final char[] _MARKER_TR_CLOSE = "/tr>".toCharArray();
422 
423     private static final char[] _MARKER_TR_OPEN = "tr>".toCharArray();
424 
425     private static final char[] _MARKER_TEXTAREA_CLOSE =
426         "/textarea>".toCharArray();
427 
428     private static final char[] _MARKER_TEXTAREA_OPEN =
429         "textarea ".toCharArray();
430 
431     private static final char[] _MARKER_UL_CLOSE = "/ul>".toCharArray();
432 
433     private static final String _SCRIPT_TYPE_JAVASCRIPT =
434         "<script type=\"text/javascript\">";
435 
436     private static final int _STATE_FOUND_ELEMENT = 3;
437 
438     private static final int _STATE_IGNORE = 1;
439 
440     private static final int _STATE_MINIFY_SCRIPT = 4;
441 
442     private static final int _STATE_MINIFY_STYLE = 5;
443 
444     private static final int _STATE_NORMAL = 0;
445 
446     private static final String _STYLE_TYPE_CSS = "<style type=\"text/css\">";
447 
448     private static final String _STRIP = "strip";
449 
450     private static Log _log = LogFactoryUtil.getLog(StripFilter.class);
451 
452 }