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