All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.context.PartialResponseWriterImpl Maven / Gradle / Ivy

/*
 * 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.myfaces.context;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import jakarta.faces.component.UIComponent;
import jakarta.faces.context.PartialResponseWriter;
import jakarta.faces.context.ResponseWriter;

import org.apache.myfaces.util.CDataEndEscapeFilterWriter;
import org.apache.myfaces.util.IllegalXmlCharacterFilterWriter;

/**
 * 

* Double buffering partial response writer * to take care if embedded CDATA blocks in update delete etc... *

* According to the spec 13.4.4.1 Writing The Partial Response * implementations have to take care to handle nested cdata blocks properly *

* This means we cannot allow nested CDATA * according to the xml spec http://www.w3.org/TR/REC-xml/#sec-cdata-sect * everything within a CDATA block is unparsed except for ]]> *

* Now we have following problem, that CDATA inserts can happen everywhere * not only within the CDATA instructions. *

* What we have to do now is to double buffer CDATA blocks until their end * and also!!! parse their content for CDATA embedding and replace it with an escaped end sequence. *

* Now parsing CDATA embedding is a little bit problematic in case of PPR because * it can happen that someone simply adds a CDATA in a javascript string or somewhere else. * Because he/she is not aware that we wrap the entire content into CDATA. * Simply encoding and decoding of the CDATA is similarly problematic * because the browser then chokes on embedded //<![CDATA[ //]]> sections *

* What we do for now is to simply remove //<![CDATA[ and //]]> * and replace all other pending cdatas with their cdata escapes * ]]> becomes <![CDATA[]]]]><![CDATA[> *

* If this causes problems in corner cases we also can add a second encoding step in * case of the cdata Javascript comment removal is not enough to cover all corner cases. *

* For now I will only implement this in the impl, due to the spec stating * that implementations are responsible of the correct CDATA handling! *

* * @author Werner Punz (latest modification by $Author$) * @version $Revision$ $Date$ */ public class PartialResponseWriterImpl extends PartialResponseWriter { static class StackEntry { private ResponseWriter writer; private Writer doubleBuffer; StackEntry(ResponseWriter writer, Writer doubleBuffer) { this.writer = writer; this.doubleBuffer = doubleBuffer; } public ResponseWriter getWriter() { return writer; } public void setWriter(ResponseWriter writer) { this.writer = writer; } public Writer getDoubleBuffer() { return doubleBuffer; } public void setDoubleBuffer(Writer doubleBuffer) { this.doubleBuffer = doubleBuffer; } } private ResponseWriter cdataDoubleBufferWriter = null; private Writer doubleBuffer = null; private List nestingStack = new ArrayList<>(4); public PartialResponseWriterImpl(ResponseWriter writer) { super(writer.cloneWithWriter(new IllegalXmlCharacterFilterWriter(writer))); } @Override public void startCDATA() throws IOException { if (!isDoubleBufferEnabled()) { super.startCDATA(); } else { cdataDoubleBufferWriter.write(""); } else { super.endCDATA(); } } /** * Close double buffer condition * This does either a normal close or a force * close in case of a force close * the entire buffer is pushed with the post processing * operations into the originating writer * * @param force if set to true the close is a forced close which in any condition * immediately pushes the buffer content into our writer with a pre operation * done upfront, in case of a false, the buffer is only swept out if our * internal CDATA nesting counter is at the nesting depth 1 * @throws IOException */ private void closeDoubleBuffer(boolean force) throws IOException { if (!isDoubleBufferEnabled()) { return; } /* * if a force close is issued we reset the condition * to 1 to reach the underlying closing block */ if (force) { while (!nestingStack.isEmpty()) { popAndEncodeCurrentStackEntry(); } } else { popAndEncodeCurrentStackEntry(); } } private void popAndEncodeCurrentStackEntry() throws IOException { nestingStack.remove(0); StackEntry parent = (nestingStack.isEmpty()) ? null : nestingStack.get(0); if (parent != null) { cdataDoubleBufferWriter = parent.getWriter(); doubleBuffer = parent.getDoubleBuffer(); } else { cdataDoubleBufferWriter = null; doubleBuffer = null; } } //--- we need to override ppr specifics to cover the case @Override public void endInsert() throws IOException { //we use a force close here to fix possible user CDATA corrections //under normal conditions the force close just processes the same //the underlying close cdata does, but nevertheless //it is better to have an additional layer of fixup closeDoubleBuffer(true); super.endInsert(); } @Override public void endUpdate() throws IOException { //we use a force close here to fix possible user CDATA corrections //under normal conditions the force close just processes the same //the underlying close cdata does, but nevertheless //it is better to have an additional layer of fixup closeDoubleBuffer(true); super.endUpdate(); //To change body of overridden methods use File | Settings | File Templates. } @Override public void endExtension() throws IOException { //we use a force close here to fix possible user CDATA corrections //under normal conditions the force close just processes the same //the underlying close cdata does, but nevertheless //it is better to have an additional layer of fixup closeDoubleBuffer(true); super.endExtension(); //To change body of overridden methods use File | Settings | File Templates. } @Override public void endEval() throws IOException { //we use a force close here to fix possible user CDATA corrections //under normal conditions the force close just processes the same //the underlying close cdata does, but nevertheless //it is better to have an additional layer of fixup closeDoubleBuffer(true); super.endEval(); //To change body of overridden methods use File | Settings | File Templates. } @Override public void endError() throws IOException { //we use a force close here to fix possible user CDATA corrections //under normal conditions the force close just processes the same //the underlying close cdata does, but nevertheless //it is better to have an additional layer of fixup closeDoubleBuffer(true); super.endError(); //To change body of overridden methods use File | Settings | File Templates. } //--- optional delegation method --- @Override public void endElement(String name) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.endElement(name); } else { super.endElement(name); } } @Override public void writeComment(Object comment) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.writeComment(comment); } else { super.writeComment(comment); } } private boolean isDoubleBufferEnabled() { return !nestingStack.isEmpty(); } @Override public void startElement(String name, UIComponent component) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.startElement(name, component); } else { super.startElement(name, component); } } @Override public void writeText(Object text, String property) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.writeText(text, property); } else { super.writeText(text, property); } } @Override public void writeText(char[] text, int off, int len) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.writeText(text, off, len); } else { super.writeText(text, off, len); } } @Override public void write(char[] cbuf, int off, int len) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.write(cbuf, off, len); } else { super.write(cbuf, off, len); } } @Override public ResponseWriter cloneWithWriter(Writer writer) { return super.cloneWithWriter(writer); } @Override public void writeURIAttribute(String name, Object value, String property) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.writeURIAttribute(name, value, property); } else { super.writeURIAttribute(name, value, property); } } @Override public void close() throws IOException { //in case of a close //we have a user error of a final CDATA block //we do some error correction here //since a close is issued we do not care about //a proper closure of the cdata block here anymore if (isDoubleBufferEnabled()) { //we have to properly close all nested cdata stacks //end end our cdata block if open closeDoubleBuffer(true); super.endCDATA(); } super.close(); } @Override public void flush() throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.flush(); } super.flush(); } @Override public void writeAttribute(String name, Object value, String property) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.writeAttribute(name, value, property); } else { super.writeAttribute(name, value, property); } } @Override public void writeText(Object object, UIComponent component, String string) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.writeText(object, component, string); } else { super.writeText(object, component, string); } } @Override public Writer append(char c) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.append(c); return this; } else { return super.append(c); } } @Override public Writer append(CharSequence csq, int start, int end) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.append(csq, start, end); return this; } else { return super.append(csq, start, end); } } @Override public Writer append(CharSequence csq) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.append(csq); return this; } else { return super.append(csq); } } @Override public void write(char[] cbuf) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.write(cbuf); } else { super.write(cbuf); } } @Override public void write(int c) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.write(c); } else { super.write(c); } } @Override public void write(String str, int off, int len) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.write(str, off, len); } else { super.write(str, off, len); } } @Override public void write(String str) throws IOException { if (isDoubleBufferEnabled()) { cdataDoubleBufferWriter.write(str); } else { super.write(str); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy