/* * Password Management Servlets (PWM) * http://www.pwm-project.org * * Copyright (c) 2006-2009 Novell, Inc. * Copyright (c) 2009-2017 The PWM Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package password.pwm.http.filter; import password.pwm.AppProperty; import password.pwm.PwmApplication; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.ContextManager; import password.pwm.http.HttpHeader; import password.pwm.http.PwmURL; import password.pwm.util.logging.PwmLogger; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.GZIPOutputStream; /** * GZip Filter Wrapper. This filter must be invoked _before_ a PwmRequest object is instantiated, else * it will cache a reference to the original response and break the application. */ public class GZIPFilter implements Filter { private static final PwmLogger LOGGER = PwmLogger.forClass(GZIPFilter.class); public void init(final FilterConfig filterConfig) throws ServletException { } public void destroy() { } @Override public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { final String acceptEncoding = ((HttpServletRequest)servletRequest).getHeader(HttpHeader.Accept_Encoding.getHttpName()); if (acceptEncoding != null && acceptEncoding.contains("gzip") && isEnabled(servletRequest)) { final GZIPHttpServletResponseWrapper gzipResponse = new GZIPHttpServletResponseWrapper((HttpServletResponse)servletResponse); gzipResponse.addHeader("Content-Encoding", "gzip"); filterChain.doFilter(servletRequest, gzipResponse); gzipResponse.finish(); } else { filterChain.doFilter(servletRequest, servletResponse); } } private boolean isEnabled(final ServletRequest servletRequest) { try { final PwmURL pwmURL = new PwmURL((HttpServletRequest) servletRequest); if (pwmURL.isResourceURL() || pwmURL.isWebServiceURL()) { return false; } } catch (Exception e) { LOGGER.error("unable to parse request url, defaulting to non-gzip: " + e.getMessage()); } final PwmApplication pwmApplication; try { pwmApplication = ContextManager.getPwmApplication((HttpServletRequest) servletRequest); return Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_ENABLE_GZIP)); } catch (PwmUnrecoverableException e) { //LOGGER.trace("unable to read http-gzip app-property, defaulting to non-gzip: " + e.getMessage()); } return false; } public static class GZIPHttpServletResponseWrapper extends HttpServletResponseWrapper { private ServletResponseGZIPOutputStream gzipStream; private ServletOutputStream outputStream; private PrintWriter printWriter; public GZIPHttpServletResponseWrapper(final HttpServletResponse response) throws IOException { super(response); } public void finish() throws IOException { if (printWriter != null) { printWriter.close(); } if (outputStream != null) { outputStream.close(); } if (gzipStream != null) { gzipStream.close(); } } @Override public void flushBuffer() throws IOException { if (printWriter != null) { printWriter.flush(); } if (outputStream != null) { outputStream.flush(); } super.flushBuffer(); } @Override public ServletOutputStream getOutputStream() throws IOException { if (printWriter != null) { throw new IllegalStateException("getWriter() has previously been invoked, can not call getOutputStream()"); } if (outputStream == null) { initGzip(); outputStream = gzipStream; } return outputStream; } @Override public PrintWriter getWriter() throws IOException { if (outputStream != null) { throw new IllegalStateException("getOutputStream() has previously been invoked, can not call getWriter()"); } if (printWriter == null) { initGzip(); printWriter = new PrintWriter(new OutputStreamWriter(gzipStream, getResponse().getCharacterEncoding())); } return printWriter; } @Override public void setContentLength(final int len) { } private void initGzip() throws IOException { gzipStream = new ServletResponseGZIPOutputStream(getResponse().getOutputStream()); } } public static class ServletResponseGZIPOutputStream extends ServletOutputStream { private final AtomicBoolean open = new AtomicBoolean(true); private ServletOutputStream servletOutputStream; private GZIPOutputStream gzipStream; public ServletResponseGZIPOutputStream(final ServletOutputStream output) throws IOException { servletOutputStream = output; gzipStream = new GZIPOutputStream(output); } @Override public void close() throws IOException { if (open.compareAndSet(true, false)) { gzipStream.close(); } } @Override public void flush() throws IOException { gzipStream.flush(); } @Override public void write(final byte[] b) throws IOException { write(b, 0, b.length); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { if (!open.get()) { throw new IOException("Stream closed!"); } gzipStream.write(b, off, len); } @Override public void write(final int b) throws IOException { if (!open.get()) { throw new IOException("Stream closed!"); } gzipStream.write(b); } /* // servlet 3.1 method public void setWriteListener(WriteListener writeListener) { servletOutputStream.setWriteListener(writeListener); } // servlet 3.1 method public boolean isReady() { return servletOutputStream.isReady(); } */ } }