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

org.apache.myfaces.renderkit.html.HtmlResponseWriterImpl 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.renderkit.html;

import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import jakarta.el.ValueExpression;

import jakarta.faces.FacesException;
import jakarta.faces.component.UIComponent;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.ResponseWriter;
import jakarta.faces.render.Renderer;

import org.apache.myfaces.config.webparameters.MyfacesConfig;
import org.apache.myfaces.core.api.shared.ComponentUtils;
import org.apache.myfaces.renderkit.ContentTypeUtils;
import org.apache.myfaces.renderkit.html.util.UnicodeEncoder;
import org.apache.myfaces.util.CommentUtils;
import org.apache.myfaces.util.lang.StreamCharBuffer;
import org.apache.myfaces.renderkit.html.util.HTML;
import org.apache.myfaces.renderkit.html.util.HTMLEncoder;
import org.apache.myfaces.core.api.shared.lang.Assert;

public class HtmlResponseWriterImpl extends ResponseWriter
{
    private static final Logger log = Logger.getLogger(HtmlResponseWriterImpl.class.getName());

    private static final String DEFAULT_CONTENT_TYPE = "text/html";
    private static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
    private static final String UTF8 = "UTF-8";

    private static final String APPLICATION_XML_CONTENT_TYPE = "application/xml";
    private static final String TEXT_XML_CONTENT_TYPE = "text/xml";

    private static final String CDATA_START = "";
    private static final String CDATA_END = "\n]]>";
    private static final String CDATA_END_NO_LINE_RETURN = "]]>";
    private static final String COMMENT_COMMENT_END = "\n//-->";
    private static final String COMMENT_END = "\n-->";

    static private final String[][] EMPTY_ELEMENT_ARR = new String[256][];

    static private final String[] A_NAMES = new String[]
    {
      "area",
    };

    static private final String[] B_NAMES = new String[]
    {
      "br",
      "base",
      "basefont",
    };

    static private final String[] C_NAMES = new String[]
    {
      "col",
    };

    static private final String[] E_NAMES = new String[]
    {
      "embed",
    };

    static private final String[] F_NAMES = new String[]
    {
      "frame",
    };

    static private final String[] H_NAMES = new String[]
    {
      "hr",
    };

    static private final String[] I_NAMES = new String[]
    {
      "img",
      "input",
      "isindex",
    };

    static private final String[] L_NAMES = new String[]
    {
      "link",
    };

    static private final String[] M_NAMES = new String[]
    {
      "meta",
    };

    static private final String[] P_NAMES = new String[]
    {
      "param",
    };

    static
    {
      EMPTY_ELEMENT_ARR['a'] = A_NAMES;
      EMPTY_ELEMENT_ARR['A'] = A_NAMES;
      EMPTY_ELEMENT_ARR['b'] = B_NAMES;
      EMPTY_ELEMENT_ARR['B'] = B_NAMES;
      EMPTY_ELEMENT_ARR['c'] = C_NAMES;
      EMPTY_ELEMENT_ARR['C'] = C_NAMES;
      EMPTY_ELEMENT_ARR['e'] = E_NAMES;
      EMPTY_ELEMENT_ARR['E'] = E_NAMES;
      EMPTY_ELEMENT_ARR['f'] = F_NAMES;
      EMPTY_ELEMENT_ARR['F'] = F_NAMES;
      EMPTY_ELEMENT_ARR['h'] = H_NAMES;
      EMPTY_ELEMENT_ARR['H'] = H_NAMES;
      EMPTY_ELEMENT_ARR['i'] = I_NAMES;
      EMPTY_ELEMENT_ARR['I'] = I_NAMES;
      EMPTY_ELEMENT_ARR['l'] = L_NAMES;
      EMPTY_ELEMENT_ARR['L'] = L_NAMES;
      EMPTY_ELEMENT_ARR['m'] = M_NAMES;
      EMPTY_ELEMENT_ARR['M'] = M_NAMES;
      EMPTY_ELEMENT_ARR['p'] = P_NAMES;
      EMPTY_ELEMENT_ARR['P'] = P_NAMES;
    }    
    

    /**
     * The writer used as output, or in other words, the one passed on the constructor
     */
    private Writer _outputWriter;
    
    /**
     * The writer we are using to store data.
     */
    private Writer _currentWriter;
    
    /**
     * The writer used to buffer script and style content
     */
    private StreamCharBuffer _buffer;
    
    private String _contentType;
    
    private String _writerContentTypeMode;
    
    /**
     * This var prevents check if the contentType is for xhtml multiple times.
     */
    private boolean _isXhtmlContentType;
    
    /**
     * Indicate the current response writer should not close automatically html elements
     * and let the writer close them.
     */
    private boolean _useStraightXml;
    
    private String _characterEncoding;
    private boolean _wrapScriptContentWithXmlCommentTag;
    private boolean _isUTF8;
    
    private String _startElementName;
    private boolean _isInsideScript = false;
    private boolean _isStyle = false;
    private Boolean _isTextArea;
    private UIComponent _startElementUIComponent;
    private boolean _startTagOpen;
    private Map _passThroughAttributesMap;
    private FacesContext _facesContext;

    private boolean _cdataOpen;
    
    private List _startedChangedElements;
    private List _startedElementsCount;
    
    public HtmlResponseWriterImpl(Writer writer, String contentType, String characterEncoding)
    {
        this(writer,contentType,characterEncoding,true);
    }

    public HtmlResponseWriterImpl(Writer writer, String contentType, String characterEncoding,
            boolean wrapScriptContentWithXmlCommentTag)
    {
        this(writer,contentType, characterEncoding, wrapScriptContentWithXmlCommentTag, 
                contentType != null && ContentTypeUtils.isXHTMLContentType(contentType) ? 
                    ContentTypeUtils.XHTML_CONTENT_TYPE : ContentTypeUtils.HTML_CONTENT_TYPE);
    }
    
    public HtmlResponseWriterImpl(Writer writer, String contentType, String characterEncoding,
             boolean wrapScriptContentWithXmlCommentTag, String writerContentTypeMode) throws FacesException
    {
        _outputWriter = writer;
        //The current writer to be used is the one used as output 
        _currentWriter = _outputWriter;
        _wrapScriptContentWithXmlCommentTag = wrapScriptContentWithXmlCommentTag;
        
        _contentType = contentType;
        if (_contentType == null)
        {
            if (log.isLoggable(Level.FINE))
            {
                log.fine("No content type given, using default content type " + DEFAULT_CONTENT_TYPE);
            }
            _contentType = DEFAULT_CONTENT_TYPE;
        }
        _writerContentTypeMode = writerContentTypeMode;
        _isXhtmlContentType = writerContentTypeMode.contains(ContentTypeUtils.XHTML_CONTENT_TYPE);
        
        _useStraightXml = _isXhtmlContentType
                && (_contentType.contains(APPLICATION_XML_CONTENT_TYPE)
                    || _contentType.contains(TEXT_XML_CONTENT_TYPE));

        if (characterEncoding == null)
        {
            if (log.isLoggable(Level.FINE))
            {
                log.fine("No character encoding given, using default character encoding "
                        + DEFAULT_CHARACTER_ENCODING);
            }
            _characterEncoding = DEFAULT_CHARACTER_ENCODING;
        }
        else
        {
            // canonize to uppercase, that's the standard format
            _characterEncoding = characterEncoding.toUpperCase();
            
            // Check if encoding is valid by javadoc of RenderKit.createResponseWriter()
            if (!Charset.isSupported(_characterEncoding))
            {
                throw new IllegalArgumentException("Encoding "+_characterEncoding
                        +" not supported by HtmlResponseWriterImpl");
            }
        }
        _isUTF8 = UTF8.equals(_characterEncoding);
        _startedChangedElements = new ArrayList();
        _startedElementsCount = new ArrayList();
    }

    public static boolean supportsContentType(String contentType)
    {
        String[] supportedContentTypes = ContentTypeUtils.getSupportedContentTypes();
        for (String supportedContentType : supportedContentTypes)
        {
            if (supportedContentType.contains(contentType))
            {
                return true;
            }
        }
        return false;
    }

    @Override
    public String getContentType()
    {
        return _contentType;
    }
    
    public String getWriterContentTypeMode()
    {
        return _writerContentTypeMode;
    }

    @Override
    public String getCharacterEncoding()
    {
        return _characterEncoding;
    }

    @Override
    public void flush() throws IOException
    {
        // API doc says we should not flush the underlying writer
        //_writer.flush();
        // but rather clear any values buffered by this ResponseWriter:
        closeStartTagIfNecessary();
    }

    @Override
    public void startDocument()
    {
        // do nothing
    }

    @Override
    public void endDocument() throws IOException
    {
        MyfacesConfig myfacesConfig = MyfacesConfig.getCurrentInstance(FacesContext.getCurrentInstance());
        if (myfacesConfig.isEarlyFlushEnabled())
        {
            _currentWriter.flush();
        }
        _facesContext = null;
    }

    @Override
    public void startElement(String name, UIComponent uiComponent) throws IOException
    {
        Assert.notNull(name, "name");

        closeStartTagIfNecessary();
        _currentWriter.write('<');

        resetStartedElement();

        _startElementName = name;
        _startElementUIComponent = uiComponent;
        _startTagOpen = true;
        _passThroughAttributesMap = _startElementUIComponent != null
                ? _startElementUIComponent.getPassThroughAttributes(false)
                : null;

        if (_passThroughAttributesMap != null)
        {
            Object value = _passThroughAttributesMap.get(Renderer.PASSTHROUGH_RENDERER_LOCALNAME_KEY);
            if (value != null)
            {
                if (value instanceof ValueExpression)
                {
                    value = ((ValueExpression)value).getValue(getFacesContext().getELContext());
                }
                String elementName = value.toString().trim();
                
                if (!name.equals(elementName))
                {
                    _startElementName = elementName;
                    _startedChangedElements.add(elementName);
                    _startedElementsCount.add(0);
                }
                _currentWriter.write(elementName);
            }
            else
            {
                _currentWriter.write(name);
            }
        }
        else
        {
            _currentWriter.write(name);
        }

        if (!_startedElementsCount.isEmpty())
        {
            int i = _startedElementsCount.size()-1;
            _startedElementsCount.set(i, _startedElementsCount.get(i)+1);
        }
        
        // Each time we start a element, it is necessary to check