Return-Path: Delivered-To: apmail-geronimo-scm-archive@www.apache.org Received: (qmail 87072 invoked from network); 22 Sep 2009 09:25:17 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 22 Sep 2009 09:25:17 -0000 Received: (qmail 65865 invoked by uid 500); 22 Sep 2009 09:25:17 -0000 Delivered-To: apmail-geronimo-scm-archive@geronimo.apache.org Received: (qmail 65795 invoked by uid 500); 22 Sep 2009 09:25:17 -0000 Mailing-List: contact scm-help@geronimo.apache.org; run by ezmlm Precedence: bulk list-help: list-unsubscribe: List-Post: Reply-To: dev@geronimo.apache.org List-Id: Delivered-To: mailing list scm@geronimo.apache.org Received: (qmail 65786 invoked by uid 99); 22 Sep 2009 09:25:16 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 22 Sep 2009 09:25:16 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 22 Sep 2009 09:25:07 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 8D9B523888CC; Tue, 22 Sep 2009 09:24:47 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r817569 - /geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/ Date: Tue, 22 Sep 2009 09:24:47 -0000 To: scm@geronimo.apache.org From: xuhaihong@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20090922092447.8D9B523888CC@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: xuhaihong Date: Tue Sep 22 09:24:43 2009 New Revision: 817569 URL: http://svn.apache.org/viewvc?rev=817569&view=rev Log: GERONIMO-4874 Improve the console filter performance (Patch from Jack Cai) Added: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstitutePrintWriter.java (with props) geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseOutputStream.java (with props) geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseWrapper.java (with props) geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteUtil.java (with props) geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteWriter.java (with props) Modified: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/XSRFHandler.java geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/XSSXSRFFilter.java Added: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstitutePrintWriter.java URL: http://svn.apache.org/viewvc/geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstitutePrintWriter.java?rev=817569&view=auto ============================================================================== --- geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstitutePrintWriter.java (added) +++ geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstitutePrintWriter.java Tue Sep 22 09:24:43 2009 @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.geronimo.console.filter; + +import java.io.PrintWriter; + +/** + * A printer writer that will replace certain keyword (</body>) with a given + * string in the output. + * @version $Rev$, $Date$ + */ +public class SubstitutePrintWriter extends PrintWriter { + + private SubstituteWriter writer; + + SubstitutePrintWriter(SubstituteWriter writer) { + super(writer); + this.writer = writer; + } + + public void reset() { + writer.reset(); + } +} Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstitutePrintWriter.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstitutePrintWriter.java ------------------------------------------------------------------------------ svn:keywords = Date Revision Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstitutePrintWriter.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseOutputStream.java URL: http://svn.apache.org/viewvc/geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseOutputStream.java?rev=817569&view=auto ============================================================================== --- geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseOutputStream.java (added) +++ geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseOutputStream.java Tue Sep 22 09:24:43 2009 @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.geronimo.console.filter; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.MalformedInputException; + +import javax.servlet.ServletOutputStream; + +/** + * A output stream that will replace certain keyword (</body>) with a + * given string in the output. + * @version $Rev$, $Date$ + */ +public class SubstituteResponseOutputStream extends ServletOutputStream { + + private static final int BUFFER_SIZE = 4086; + + private static final int PROCESS_THRESHOLD = 128; + + // Console use UTF-8 encoding. In UTF-8, max length for one character is 4. + private static final int MALFORMED_INPUT_MAX_LENGTH = 4; + + private String substitute = null; + + private String outputCharset = null; + + private OutputStream stream = null; + + private CharsetDecoder cd = null; + + private ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE); + + private CharBuffer cb = CharBuffer.allocate(BUFFER_SIZE); + + private boolean found = false; + + /** + * Construct a output stream which will scan the input and do substitution + * for the keyword "</body>". + * + * @param substitute + * The text that will replace "</body>" + * @param outputCharset + * The charset that will be used to encode the output + * @param os + * The output stream + */ + public SubstituteResponseOutputStream(String substitute, String outputCharset, OutputStream os) { + if (substitute == null || outputCharset == null || os == null) { + throw new NullPointerException(); + } + this.substitute = substitute; + this.outputCharset = outputCharset; + this.stream = os; + Charset cs = Charset.forName(outputCharset); + cd = cs.newDecoder().onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPLACE); + } + + /* + * Decode the bytes in the byte buffer and append to the char buffer. + * + * The byte & char buffer should be ready for "put" before entering this + * method. They are still ready for "put" after exiting this method. + * + * @param flushBuffer Whether to flush out all the contents in the byte + * buffer + * + * @throws IOException + */ + private void decodeBuffer(boolean flushBuffer) throws IOException { + if (!flushBuffer && bb.position() < PROCESS_THRESHOLD) { + return; + } + bb.flip(); + cd.reset(); + if (bb.hasRemaining()) { + CoderResult cr = cd.decode(bb, cb, true); + cd.flush(cb); + if (cr.isMalformed()) { + // Move the tail bytes to the head + int tailLength = bb.remaining(); + if (tailLength >= MALFORMED_INPUT_MAX_LENGTH) { + // We only expect the bytes of one character to be broken + throw new MalformedInputException(tailLength); + } + } + bb.compact(); + } else { + bb.clear(); + } + } + + /* + * Process the bytes in the buffer. This involves: 1. Decode the bytes and + * put into the char buffer; 2. Scan the characters in the buffer and do + * replacement. + * + * The byte & char buffer should be ready for "put" before entering this + * method. They are still ready for "put" after exiting this method. + * + * @param flushBuffer Whether to flush out all the contents in the buffer + * @param endOfInput Whether this is the end of input + * + * @throws IOException + */ + private void processBuffer(boolean flushBuffer, boolean endOfInput) throws IOException { + decodeBuffer(flushBuffer || endOfInput); + if (!endOfInput && !flushBuffer && cb.position() < PROCESS_THRESHOLD) { + return; + } + cb.flip(); + found = SubstituteUtil.processSubstitute(cb, substitute, endOfInput, outputCharset, stream); + // Write the tail bytes in the byte buffer if required + if (bb.position() > 0 && (found || endOfInput)) { + bb.flip(); + while (bb.hasRemaining()) { + stream.write(bb.get()); + } + bb.clear(); + } + } + + /* + * (non-Javadoc) + * + * @see java.io.OutputStream#write(int) + */ + @Override + public void write(int b) throws IOException { + if (found) { + stream.write(b); + } else { + bb.put((byte) b); + processBuffer(false, false); + } + } + + /* + * (non-Javadoc) + * + * @see java.io.OutputStream#write(int) + */ + @Override + public void write(byte[] content, int offset, int length) throws IOException { + if (found) { + stream.write(content, offset, length); + return; + } + int boundary = offset + length; + int batchOffset = offset; + int batchLength; + while (batchOffset < boundary) { + if (found) { + stream.write(content, batchOffset, boundary - batchOffset); + break; + } else { + if (boundary - batchOffset < bb.remaining()) { + batchLength = boundary - batchOffset; + } else { + batchLength = bb.remaining(); + } + bb.put(content, batchOffset, batchLength); + processBuffer(false, false); + batchOffset = batchOffset + batchLength; + } + } + } + + /* + * (non-Javadoc) + * + * @see java.io.OutputStream#print(String) + */ + @Override + public void print(String s) throws IOException { + if (found) { + super.print(s); + return; + } + if (bb.position() > 0) { + // Process the buffered bytes + decodeBuffer(true); + if (bb.position() > 0) { + // There are still malformed input remaining, even though we + // are going to write a string. This should not happen. + throw new MalformedInputException(bb.position()); + } + } + // Process the string without encoding/decoding overhead + char[] content = s.toCharArray(); + int offset = 0; + int length; + while (offset < content.length) { + if (found) { + super.print(s.substring(offset)); + break; + } else { + if (content.length - offset < cb.remaining()) { + length = content.length - offset; + } else { + length = cb.remaining(); + } + cb.put(content, offset, length); + processBuffer(false, false); + offset = offset + length; + } + } + } + + /* + * (non-Javadoc) + * + * @see java.io.OutputStream#close() + */ + @Override + public void close() throws IOException { + if (!found) { + processBuffer(true, true); + } + stream.flush(); + stream.close(); + } + + /* + * (non-Javadoc) + * + * @see java.io.OutputStream#flush() + */ + @Override + public void flush() throws IOException { + if (!found) { + // Try to write out as much as possible, but some text might be + // still buffered in order to handle broken keyword + processBuffer(true, false); + } + stream.flush(); + } + + /** + * Reset this output stream. Clear the buffers. + */ + public void reset() { + bb.clear(); + cb.clear(); + found = false; + } +} Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseOutputStream.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseOutputStream.java ------------------------------------------------------------------------------ svn:keywords = Date Revision Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseOutputStream.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseWrapper.java URL: http://svn.apache.org/viewvc/geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseWrapper.java?rev=817569&view=auto ============================================================================== --- geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseWrapper.java (added) +++ geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseWrapper.java Tue Sep 22 09:24:43 2009 @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.geronimo.console.filter; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A response wrapper that will replace certain keyword () with a given + * string in the output. + * @version $Rev$, $Date$ + */ +public class SubstituteResponseWrapper extends HttpServletResponseWrapper { + private static final Logger log = LoggerFactory.getLogger(SubstituteResponseWrapper.class); + + private SubstituteResponseOutputStream stream = null; + private SubstitutePrintWriter writer = null; + private String substitute = null; + + public SubstituteResponseWrapper(HttpServletResponse response, + String substitute) { + super(response); + this.substitute = substitute; + } + + private boolean substituteRequired() { + String cType = getContentType(); + if (cType != null) { + // only update the content if it is HTML + if (cType.toLowerCase().indexOf("html") != -1) { + return true; + } + } + return false; + } + + @Override + public void flushBuffer() throws IOException { + if (substituteRequired()) { + if (writer != null) { + writer.flush(); + } else if (stream != null) { + stream.flush(); + } + } else { + super.flushBuffer(); + } + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + log.debug("Getting a stream..."); + if (!substituteRequired()) { + return super.getOutputStream(); + } else if (writer != null) { + throw new IllegalStateException( + "getWriter() has already been called on this response."); + } else if (stream == null) { + stream = new SubstituteResponseOutputStream(substitute, + getCharacterEncoding(), super.getOutputStream()); + } + return stream; + } + + @Override + public PrintWriter getWriter() throws IOException { + log.debug("Getting a writer..."); + if (!substituteRequired()) { + return super.getWriter(); + } else if (stream != null) { + throw new IllegalStateException( + "getStream() has already been called on this response."); + } else if (writer == null) { + writer = new SubstitutePrintWriter(new SubstituteWriter( + substitute, getCharacterEncoding(), super + .getOutputStream())); + } + return writer; + + } + + @Override + public void reset() { + log.debug("Resetting..."); + super.reset(); + // If no exception from the wrapped response, let's reset too + if (substituteRequired()) { + if (stream != null) { + stream.reset(); + } else if (writer != null) { + writer.reset(); + } + } + } + + @Override + public void resetBuffer() { + log.debug("Resetting buffer..."); + super.resetBuffer(); + if (substituteRequired()) { + // If no exception from the wrapped response, let's reset too + if (stream != null) { + stream.reset(); + } else if (writer != null) { + writer.reset(); + } + } + } +} Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseWrapper.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseWrapper.java ------------------------------------------------------------------------------ svn:keywords = Date Revision Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteResponseWrapper.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteUtil.java URL: http://svn.apache.org/viewvc/geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteUtil.java?rev=817569&view=auto ============================================================================== --- geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteUtil.java (added) +++ geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteUtil.java Tue Sep 22 09:24:43 2009 @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.geronimo.console.filter; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.CharBuffer; +import java.util.regex.Pattern; + +/** + * @version $Rev$, $Date$ + */ +public class SubstituteUtil { + + private final static String KEYWORD = ""; + + private final static Pattern SEARCH_PATTERN = Pattern.compile("(?i)" + KEYWORD); + + /** + * Scan the characters in the buffer and do replacement. + * + * The char buffer should be ready for "get" before entering this method, + * and will change to be ready for "put" after exiting this method. + * + * @param cb + * The char buffer to be scanned + * @param replacement + * The replacement string that will be used to replace found + * occurrences of the keyword + * @param endOfInput + * Whether this is the last batch to scan + * @param outputCharset + * The charset to encode the output + * @param os + * The output stream to receive the output + * @return true if the keyword is found, otherwise false + * @throws IOException + */ + public static boolean processSubstitute(CharBuffer cb, String replacement, boolean endOfInput, String outputCharset, OutputStream os) throws IOException { + int remaining = cb.remaining(); + if (remaining == 0) { + cb.clear(); + return false; + } + String result = SEARCH_PATTERN.matcher(cb).replaceFirst(replacement); + cb.clear(); + // For simplicity, we assume that the length of the replacement is + // different from the keyword, which is definitely true in Geronimo + // admin console's case + if (result.length() != remaining) { + // Replacement happened, we've got a match + os.write(result.getBytes(outputCharset)); + return true; + } else if (endOfInput) { + // End of input, write everything out + os.write(result.getBytes(outputCharset)); + return false; + } else { + // Push back the last N chars so that we don't break a keyword + char tail = result.charAt(result.length() - 1); + if (tail != ' ' && tail != '\n' && tail != '\r') { + int textTailLength = Math.min(result.length(), KEYWORD.length() - 1); + int textTailOffset = result.length() - textTailLength; + cb.put(result.substring(textTailOffset)); + if (textTailOffset > 0) { + os.write(result.substring(0, textTailOffset).getBytes(outputCharset)); + } + } else { + os.write(result.getBytes(outputCharset)); + } + return false; + } + } +} Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteUtil.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteUtil.java ------------------------------------------------------------------------------ svn:keywords = Date Revision Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteUtil.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteWriter.java URL: http://svn.apache.org/viewvc/geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteWriter.java?rev=817569&view=auto ============================================================================== --- geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteWriter.java (added) +++ geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteWriter.java Tue Sep 22 09:24:43 2009 @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.geronimo.console.filter; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; +import java.nio.CharBuffer; + +/** + * A writer that will replace certain keyword (</body>) with a given string in + * the output. + * @version $Rev$, $Date$ + */ +public class SubstituteWriter extends Writer { + + // Default internal buffer size + private static final int BUFFER_SIZE = 4086; + + // Threshold for processing buffered content + private static final int PROCESS_THRESHOLD = 128; + + private String substitute = null; + + private String outputCharset = null; + + private OutputStream stream = null; + + private CharBuffer cb = CharBuffer.allocate(BUFFER_SIZE); + + private boolean found = false; + + public SubstituteWriter(String substitute, String outputCharset, OutputStream os) { + if (substitute == null || outputCharset == null || os == null) { + throw new NullPointerException(); + } + this.substitute = substitute; + this.outputCharset = outputCharset; + this.stream = os; + } + + @Override + public void close() throws IOException { + synchronized (lock) { + if (!found) { + cb.flip(); + found = SubstituteUtil.processSubstitute(cb, substitute, true, outputCharset, stream); + } + stream.close(); + } + } + + @Override + public void flush() throws IOException { + synchronized (lock) { + if (!found) { + // Try to write out as much as possible, but some text might be + // still buffered in order to handle broken keyword + cb.flip(); + found = SubstituteUtil.processSubstitute(cb, substitute, false, outputCharset, stream); + } + stream.flush(); + } + } + + @Override + public void write(char[] content, int offset, int length) throws IOException { + synchronized (lock) { + // Skip replacement if already found one occurrence + if (found) { + String s = new String(content, offset, length); + stream.write(s.getBytes(outputCharset)); + return; + } + // Process the content in batches + int boundary = offset + length; + int batchOffset = offset; + int batchLength; + while (batchOffset < boundary) { + if (found) { + stream.write(new String(content, batchOffset, boundary - batchOffset).getBytes(outputCharset)); + break; + } else { + if (boundary - batchOffset < cb.remaining()) { + batchLength = boundary - batchOffset; + } else { + batchLength = cb.remaining(); + } + cb.put(content, batchOffset, batchLength); + if (cb.position() > PROCESS_THRESHOLD) { + cb.flip(); + found = SubstituteUtil.processSubstitute(cb, substitute, false, outputCharset, stream); + } + batchOffset = batchOffset + batchLength; + } + } + } + } + + public void reset() { + synchronized (lock) { + cb.clear(); + found = false; + } + } +} Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteWriter.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteWriter.java ------------------------------------------------------------------------------ svn:keywords = Date Revision Propchange: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/SubstituteWriter.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/XSRFHandler.java URL: http://svn.apache.org/viewvc/geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/XSRFHandler.java?rev=817569&r1=817568&r2=817569&view=diff ============================================================================== --- geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/XSRFHandler.java (original) +++ geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/XSRFHandler.java Tue Sep 22 09:24:43 2009 @@ -25,7 +25,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Random; -import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -49,8 +48,6 @@ private static final String XSRF_UNIQUEID = "formId"; private static final String XSRF_JS_FILENAME = "/XSRF.js"; private final static String XSRF_JS_UNIQUEID = "<%XSRF_UNIQUEID%>"; - private final static String SEARCH_PATTERN = "(?i)"; - private static final Pattern regexPattern = Pattern.compile(SEARCH_PATTERN); private Map sessionMap = Collections.synchronizedMap(new HashMap()); private String xsrfJS; @@ -192,53 +189,26 @@ } //----- Response handler routines ----- - /** - * Main response handler, which appends our XSRF JavaScript with the - * unique session token to any HTML response content that includes a - * form tag. + * Get XSRF JavaScript containing the unique session token. + * * @param hreq - * @param hres */ - public void updateResponse(HttpServletRequest hreq, FilterResponseWrapper hres) throws IOException { + public String getReplacement(HttpServletRequest hreq) throws IOException { // get the JavaScript file we're going to append to it - String updatedXsrfJS; String uniqueId = getSession(hreq); if (xsrfJS == null) { log.error("No JavaScript to append to the response!"); - } - else if (uniqueId == null) { - // this should only happen for user logout or session timeout, so ignore + return null; + } else if (uniqueId == null) { + // this should only happen for user logout or session timeout, so + // ignore log.debug("HttpSession is null!"); + return null; + } else { + // update the JavaScript with the uniqueId for this session + return xsrfJS.replace(XSRF_JS_UNIQUEID, uniqueId); } - else { - String cType = hres.getContentType(); - if (cType != null) { - // only update the content if it is HTML - if (cType.toLowerCase().indexOf("html") != -1) { - // get the response content - String content = new String(hres.getOutput(), "UTF-8"); - // update the JavaScript with the uniqueId for this session - updatedXsrfJS = xsrfJS.replace(XSRF_JS_UNIQUEID, uniqueId); - // update the response to contain the JS fragment - content = regexPattern.matcher(content).replaceAll(updatedXsrfJS); - log.info("Updated HTML content with XSRF JavaScript for requestURI=" + hreq.getRequestURI()); - //log.debug("Updated content =" + content); - // update the ResponseOutputStream content - hres.setOutput(content); - } - else { - // we don't want to try updating non-HTML content with our JavaScript - log.debug("Not updating requestURI=" + hreq.getRequestURI() + " due to ContentType = " + cType); - } - } - else { - // no ContentType provided, so ignore this content - log.debug("Not updating requestURI=" + hreq.getRequestURI() + " due to NO ContentType"); - } - } - // write out our updated HttpServletResponse - hres.writeOutput(); } /** Modified: geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/XSSXSRFFilter.java URL: http://svn.apache.org/viewvc/geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/XSSXSRFFilter.java?rev=817569&r1=817568&r2=817569&view=diff ============================================================================== --- geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/XSSXSRFFilter.java (original) +++ geronimo/server/branches/2.2/plugins/console/console-filter/src/main/java/org/apache/geronimo/console/filter/XSSXSRFFilter.java Tue Sep 22 09:24:43 2009 @@ -120,14 +120,14 @@ } //----------------------------------------------- // Call other filters and eventually the Servlet + // Update the response with our XSRF FORM protection code //----------------------------------------------- - FilterResponseWrapper whres = new FilterResponseWrapper((HttpServletResponse)response); + String replacement = xsrf.getReplacement(hreq); + ServletResponse whres = response; + if (replacement != null ) { + whres = new SubstituteResponseWrapper((HttpServletResponse)response, replacement); + } chain.doFilter(hreq, whres); - - //------------------------------------------------------------------- - // Update and commit the response with our XSRF FORM protection code - //------------------------------------------------------------------- - xsrf.updateResponse(hreq, whres); } else { log.debug("Request not HttpServletRequest and/or Response not HttpServletResponse");