/* DHtmlLayoutFilter.java Purpose: Description: History: Wed Nov 30 00:49:59 2005, Created by tomyeh Copyright (C) 2005 Potix Corporation. All Rights Reserved. {{IS_RIGHT This program is distributed under LGPL Version 2.1 in the hope that it will be useful, but WITHOUT ANY WARRANTY. }}IS_RIGHT */ package org.zkoss.zk.ui.http; import java.io.IOException; import java.io.OutputStream; import java.io.StringWriter; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zkoss.lang.Strings; import org.zkoss.web.servlet.Servlets; import org.zkoss.web.servlet.http.HttpBufferedResponse; import org.zkoss.web.servlet.http.Https; import org.zkoss.zk.ui.Desktop; import org.zkoss.zk.ui.Execution; import org.zkoss.zk.ui.Page; import org.zkoss.zk.ui.Session; import org.zkoss.zk.ui.UiException; import org.zkoss.zk.ui.WebApp; import org.zkoss.zk.ui.impl.RequestInfoImpl; import org.zkoss.zk.ui.metainfo.PageDefinition; import org.zkoss.zk.ui.sys.RequestInfo; import org.zkoss.zk.ui.sys.SessionCtrl; import org.zkoss.zk.ui.sys.UiFactory; import org.zkoss.zk.ui.sys.WebAppCtrl; /** * Used to post-process the response into a ZK page. * It is useful if you have a servlet using servlets, JSP or other * technology. * * <p>How to use: * <ul> * <li>Decorates the servlet to embed the ZK components and * attributes, such as zk:onClick="...".</li> * <li>Maps the servlet to the filter in web.xml</li> * </ul> * * @author tomyeh */ public class DHtmlLayoutFilter implements Filter { private static final Logger log = LoggerFactory.getLogger(DHtmlLayoutFilter.class); private ServletContext _ctx; private String _ext = "html"; private String _charset = "UTF-8"; private boolean _compress = true; /** Processes the content * @since 3.0.0 */ protected void process(HttpServletRequest request, HttpServletResponse response, String content) throws ServletException, IOException { if (log.isDebugEnabled()) log.debug("Content to filter:\n" + content); final WebManager webman = WebManager.getWebManager(_ctx); final WebApp wapp = webman.getWebApp(); final WebAppCtrl wappc = (WebAppCtrl) wapp; final Session sess = webman.getSession(_ctx, request); final Object old = I18Ns.setup(sess, request, response, _charset); try { final String path = Https.getThisServletPath(request); final Desktop desktop = webman.getDesktop(sess, request, response, path, true); if (desktop == null) //forward or redirect return; final RequestInfo ri = new RequestInfoImpl(wapp, sess, desktop, request, null); ((SessionCtrl) sess).notifyClientRequest(true); final UiFactory uf = wappc.getUiFactory(); final PageDefinition pagedef = uf.getPageDefinitionDirectly(ri, content, _ext); final Page page = WebManager.newPage(uf, ri, pagedef, response, path); final Execution exec = new ExecutionImpl(_ctx, request, response, desktop, page); final StringWriter out = new StringWriter(4096 * 2); wappc.getUiEngine().execNewPage(exec, pagedef, page, out); //bug 1738368: Jetty refuses wrong content length //so we have to calculate it again here //(Note: we have to set content length since the servlet //being filtering might set content-length, which is, //of course, wrong String cs = response.getCharacterEncoding(); if (cs == null || cs.length() == 0) cs = _charset != null ? _charset : "UTF-8"; final String result = out.toString(); try { final OutputStream os = response.getOutputStream(); //Call it first to ensure getWrite() is not called yet byte[] data = result.getBytes(cs); if (_compress && !Servlets.isIncluded(request) && data.length > 200) { byte[] bs = Https.gzip(request, response, null, data); if (bs != null) data = bs; //yes, browser support compress } response.setContentLength(data.length); os.write(data); } catch (IllegalStateException ex) { //getWriter was called response.getWriter().write(result); } } catch (UiException ex) { log.error("Failed to process:\n" + content); throw ex; } finally { I18Ns.cleanup(request, old); } } /** Filters the content to make it legal XML if possible. * Currently, it only removes <!DOCTYPE ...//DTD HTML...>, * since it makes it invalid XML. Refer to Bug 1702216. * * <p>Note: we cannot remove all DOCTYPE. Refer to Bug 1720620. */ private static String xmlfilter(StringBuffer sb) { for (int j = 0, len = sb.length(); j < len; ++j) { char cc = sb.charAt(j); if (cc == '<') { if (++j < len && sb.charAt(j) == '!') { j = Strings.skipWhitespaces(sb, j + 1); if (j + 7 < len && "DOCTYPE".equalsIgnoreCase(sb.substring(j, j + 7))) { for (int k = j += 7; k < len; ++k) { if (sb.charAt(k) == '>') { if (shallFilter(sb.substring(j, k))) return sb.substring(k + 1); break; } } } } break; } } return sb.toString(); } private static boolean shallFilter(String pubId) { pubId = pubId.toUpperCase(); int j = pubId.indexOf("//DTD"); if (j >= 0) { j = Strings.skipWhitespaces(pubId, j + 5); return j + 4 < pubId.length() && "HTML".equals(pubId.substring(j, j + 4)); } return false; //unknown => don't filter out (safer) } //-- Filter --// public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { final StringWriter sw = new StringWriter(4096 * 2); final HttpServletResponse hres = (HttpServletResponse) response; final HttpBufferedResponse hbufres = (HttpBufferedResponse) HttpBufferedResponse.getInstance(hres, sw); chain.doFilter(request, hbufres); //Bug 1673839: servlet might redirect if (!hbufres.isSendRedirect()) process((HttpServletRequest) request, hres, xmlfilter(sw.getBuffer())); } public void destroy() { } public final void init(FilterConfig config) throws ServletException { _ctx = config.getServletContext(); String param = config.getInitParameter("extension"); if (param != null && param.length() > 0) _ext = param; param = config.getInitParameter("charset"); if (param != null) _charset = param.length() > 0 ? param : null; param = config.getInitParameter("compress"); if (param != null) _compress = "true".equals(param); } }