/*
 * Copyright 2010-2016 Steve Chaloner
 *
 * Licensed 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 be.objectify.deadbolt.java;

import be.objectify.deadbolt.java.cache.HandlerCache;
import be.objectify.deadbolt.java.models.PatternType;
import com.typesafe.config.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.mvc.Http;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction;
import java.util.function.Supplier;

/**
 * Provides the entry point for view-level annotations.
 *
 * @author Steve Chaloner (steve@objectify.be)
 */
@Singleton
public class ViewSupport
{
    private static final Logger LOGGER = LoggerFactory.getLogger(ViewSupport.class);

    public final long timeout;

    private final HandlerCache handlerCache;

    private final TemplateFailureListener failureListener;

    private final BiFunction<Long, TimeoutException, Boolean> timeoutHandler;

    private final ConstraintLogic constraintLogic;

    @Inject
    public ViewSupport(final Config config,
                       final HandlerCache handlerCache,
                       final TemplateFailureListenerProvider failureListener,
                       final ConstraintLogic constraintLogic)
    {
        this.handlerCache = handlerCache;
        this.failureListener = failureListener.get();
        this.constraintLogic = constraintLogic;
        this.timeout = config.getLong("deadbolt.java.view-timeout");
        LOGGER.info("Default timeout period for blocking views is [{}]ms", this.timeout);
        this.timeoutHandler = (timeoutInMillis, e) ->
        {
            LOGGER.error("Timeout when attempting to complete future within [{}]ms.  Denying access to resource.",
                         timeoutInMillis,
                         e);
            this.failureListener.failure("Error when checking view constraint: " + e.getMessage(),
                                         timeoutInMillis);
            return false;
        };
    }

    /**
     * Used for restrict tags in the template.
     *
     * @param roles a list of String arrays.  Within an array, the roles are ANDed.  The arrays in the list are OR'd.
     * @return true if the view can be accessed, otherwise false
     */
    public boolean viewRestrict(final List<String[]> roles,
                                final DeadboltHandler handler,
                                final Optional<String> content,
                                final long timeoutInMillis,
                                final Http.RequestHeader requestHeader) throws Throwable
    {
        boolean allowed;
        try
        {
            allowed = constraintLogic.restrict(requestHeader,
                                               handler(handler),
                                               content,
                                               () -> roles,
                                               rh -> CompletableFuture.completedFuture(Boolean.TRUE),
                                               (rh, dh, cnt) -> CompletableFuture.completedFuture(Boolean.FALSE),
                                               ConstraintPoint.TEMPLATE)
                                     .toCompletableFuture()
                                     .get(timeoutInMillis,
                                          TimeUnit.MILLISECONDS);

        }
        catch (TimeoutException e)
        {
            allowed = timeoutHandler.apply(timeoutInMillis,
                                           e);
        }
        return allowed;
    }

    /**
     * Used for dynamic tags in the template.
     *
     * @param name the name of the resource
     * @param meta meta information on the resource
     * @return true if the view can be accessed, otherwise false
     */
    public boolean viewDynamic(final String name,
                               final Optional<String> meta,
                               final DeadboltHandler handler,
                               final Optional<String> content,
                               final long timeoutInMillis,
                               final Http.RequestHeader requestHeader) throws Throwable
    {
        boolean allowed;
        try
        {
            allowed = constraintLogic.dynamic(requestHeader,
                                              handler(handler),
                                              content,
                                              name,
                                              meta,
                                              rh -> CompletableFuture.completedFuture(Boolean.TRUE),
                                              (rh, dh, cnt) -> CompletableFuture.completedFuture(Boolean.FALSE),
                                              ConstraintPoint.TEMPLATE)
                                     .toCompletableFuture()
                                     .get(timeoutInMillis,
                                          TimeUnit.MILLISECONDS);
        }
        catch (TimeoutException e)
        {
            allowed = timeoutHandler.apply(timeoutInMillis,
                                           e);
        }
        return allowed;
    }

    /**
     * Used for subjectPresent tags in the template.
     *
     * @return true if the view can be accessed, otherwise false
     */
    public boolean viewSubjectPresent(final DeadboltHandler handler,
                                      final Optional<String> content,
                                      final long timeoutInMillis,
                                      final Http.RequestHeader requestHeader) throws Throwable
    {
        boolean allowed;
        try
        {
            allowed = constraintLogic.subjectPresent(requestHeader,
                                                     handler == null ? handlerCache.get()
                                                                     : handler,
                                                     content,
                                                     (rh, dh, cnt) -> CompletableFuture.completedFuture(Boolean.TRUE),
                                                     (rh, dh, cnt) -> CompletableFuture.completedFuture(Boolean.FALSE),
                                                     ConstraintPoint.TEMPLATE)
                                     .toCompletableFuture()
                                     .get(timeoutInMillis,
                                          TimeUnit.MILLISECONDS);
        }
        catch (TimeoutException e)
        {
            allowed = timeoutHandler.apply(timeoutInMillis,
                                           e);
        }
        return allowed;
    }

    /**
     * Used for subjectNotPresent tags in the template.
     *
     * @return true if the view can be accessed, otherwise false
     */
    public boolean viewSubjectNotPresent(final DeadboltHandler handler,
                                         final Optional<String> content,
                                         final long timeoutInMillis,
                                         final Http.RequestHeader requestHeader) throws Throwable
    {
        boolean allowed;
        try
        {
            allowed = constraintLogic.subjectNotPresent(requestHeader,
                                                        handler(handler),
                                                        content,
                                                        (rh, dh, cnt) -> CompletableFuture.completedFuture(Boolean.FALSE),
                                                        (rh, dh, cnt) -> CompletableFuture.completedFuture(Boolean.TRUE),
                                                        ConstraintPoint.TEMPLATE)
                                     .toCompletableFuture()
                                     .get(timeoutInMillis,
                                          TimeUnit.MILLISECONDS);
        }
        catch (TimeoutException e)
        {
            allowed = timeoutHandler.apply(timeoutInMillis,
                                           e);
        }
        return allowed;
    }

    public boolean viewPattern(final String value,
                               final PatternType patternType,
                               final Optional<String> meta,
                               final boolean invert,
                               final DeadboltHandler handler,
                               final Optional<String> content,
                               final long timeoutInMillis,
                               final Http.RequestHeader requestHeader) throws Exception
    {
        boolean allowed;
        try
        {
            allowed = constraintLogic.pattern(requestHeader,
                                              handler(handler),
                                              content,
                                              value,
                                              patternType,
                                              meta,
                                              invert,
                                              rh -> CompletableFuture.completedFuture(Boolean.TRUE),
                                              (rh, dh, cnt) -> CompletableFuture.completedFuture(Boolean.FALSE),
                                              ConstraintPoint.TEMPLATE)
                                     .toCompletableFuture()
                                     .get(timeoutInMillis,
                                          TimeUnit.MILLISECONDS);
        }
        catch (TimeoutException e)
        {
            allowed = timeoutHandler.apply(timeoutInMillis,
                                           e);
        }

        return allowed;
    }

    /**
     * Used for role-based permissions tags in the template.
     *
     * @param roleName the role name that is the key for the permission set
     * @return true if the view can be accessed, otherwise false
     */
    public boolean viewRoleBasedPermissions(final String roleName,
                                            final DeadboltHandler handler,
                                            final Optional<String> content,
                                            final long timeoutInMillis,
                                            final Http.RequestHeader requestHeader) throws Throwable
    {
        boolean allowed;
        try
        {
            allowed = constraintLogic.roleBasedPermissions(requestHeader,
                                                           handler(handler),
                                                           content,
                                                           roleName,
                                                           rh -> CompletableFuture.completedFuture(Boolean.TRUE),
                                                           (rh, dh, cnt) -> CompletableFuture.completedFuture(Boolean.FALSE),
                                                           ConstraintPoint.TEMPLATE)
                                     .toCompletableFuture()
                                     .get(timeoutInMillis,
                                          TimeUnit.MILLISECONDS);

        }
        catch (TimeoutException e)
        {
            allowed = timeoutHandler.apply(timeoutInMillis,
                                           e);
        }
        return allowed;
    }

    private DeadboltHandler handler(final DeadboltHandler handler)
    {
        return handler == null ? handlerCache.get()
                               : handler;
    }
}


// Copyright (c) 2012 Jeff Ichnowski
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//     * Redistributions of source code must retain the above
//       copyright notice, this list of conditions and the following
//       disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials
//       provided with the distribution.
//
//     * Neither the name of the OWASP nor the names of its
//       contributors may be used to endorse or promote products
//       derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package org.owasp.encoder;

import java.nio.CharBuffer;
import java.nio.charset.CoderResult;

/**
 *
This is the low-level encoding API.  For each flavor of encoding
 * there is an instance of this class that performs the actual
 * encoding.  Overriding and implementing Encoders outside of the
 * OWASP Encoder's project is not currently supported.

 *
 *
Unless otherwise documented, instances of these classes are
 * thread-safe.  Encoders implementations do not generally carry
 * state, and if they do the state will be flush with a call to {@link
 * #encode(java.nio.CharBuffer, java.nio.CharBuffer, boolean)} with
 * {@code endOfInput} set to {@code true}.

 *
 *
To use an Encoder instance directly, repeatedly call {@link
 * #encode(java.nio.CharBuffer, java.nio.CharBuffer, boolean)} with
 * the {@code endOfInput} parameter set to {@code false} while there
 * is (the possibility of) more input to encode.  Once there is no
 * more input to encode, call {@link #encode(java.nio.CharBuffer,
 * java.nio.CharBuffer, boolean)} with {@code endOfInput} set to
 * {@code true} until the method returns {@link
 * java.nio.charset.CoderResult#UNDERFLOW}.

 *
 *
In general, this class is not expected to be needed directly.
 * Use the {@link Encode} fluent interface for encoding Strings or
 * {@link EncodedWriter} for large blocks of contextual encoding.

 *
 * @author Jeff Ichnowski
 * @see Encode
 * @see EncodedWriter
 */
public abstract class Encoder {
    /**
     * Hexadecimal conversion array.  Package private to prevent corruption.
     */
    static final char[] HEX = "0123456789abcdef".toCharArray();

    /**
     * Bit-shift used for encoding values in hexadecimal.
     */
    static final int HEX_SHIFT = 4;

    /**
     * Bit-mask used for encoding values in hexadecimal.
     */
    static final int HEX_MASK = 0xf;

    /**
     * Package-private constructor to prevent having to support
     * external implementations of this class.  This may be opened up
     * in future releases.
     */
    Encoder() {}

    /**
     *
This is the kernel of encoding.  Currently only CharBuffers
     * backed by arrays (i.e. {@link java.nio.CharBuffer#hasArray()}
     * returns {@code true}) are supported.  Using a
     * direct-mapped CharBuffer will result in an
     * UnsupportedOperationException, though this behavior
     * may change in future releases.

     *
     *
This method should be called repeatedly while {@code
     * endOfInput} set to {@code false} while there is more input.
     * Once there is no more input, this method should be called
     * {@code endOfInput} set to {@code false} until {@link
     * java.nio.charset.CoderResult#UNDERFLOW} is returned.

     *
     *
After any call to this method, except when {@code
     * endOfInput} is {@code true} and the method returns {@code
     * UNDERFLOW}, there may be characters left to encode in the
     * {@code input} buffer (i.e. {@code input.hasRemaining() ==
     * true}).  This will happen when the encoder needs to see more
     * input before determining what to do--for example when encoding
     * for CDATA, if the input ends with {@code "foo]]"}, the encoder
     * will need to see the next character to determine if it is a ">"
     * or not.

     *
     *
Example usage:

     *

     *   CharBuffer input = CharBuffer.allocate(1024);
     *   CharBuffer output = CharBuffer.allocate(1024);
     *   CoderResult cr;
     *   // assuming doRead fills in the input buffer or
     *   // returns -1 at end of input
     *   while(doRead(input) != -1) {
     *     input.flip();
     *     for (;;) {
     *       cr = encoder.encode(input, output, false);
     *       if (cr.isUnderflow()) {
     *         break;
     *       }
     *       if (cr.isOverflow()) {
     *         // assuming doWrite flushes the encoded
     *         // characters somewhere.
     *         output.flip();
     *         doWrite(output);
     *         output.compact();
     *       }
     *     }
     *     input.compact();
     *   }
     *
     *   // at end of input
     *   input.flip();
     *   do {
     *     cr = encoder.encode(input, output, true);
     *     output.flip();
     *     doWrite(output);
     *     output.compact();
     *   } while (cr.isOverflow());
     *

     *
     * @param input the input buffer to encode
     * @param output the output buffer to receive the encoded results
     * @param endOfInput set to {@code true} if there is no more input, and any
     * remaining characters at the end of input will either be encoded or
     * replaced as invalid.
     * @return Either {@link java.nio.charset.CoderResult#UNDERFLOW}
     * or {@link java.nio.charset.CoderResult#OVERFLOW}.  No other
     * CoderResult value will be returned.  Characters or sequences
     * that might conceivably return and invalid or unmappable
     * character result (as part of the nio Charset API) are
     * automatically replaced to avoid security implications.
     */
    public CoderResult encode(CharBuffer input, CharBuffer output, boolean endOfInput) {
        if (input.hasRemaining()) {
            if (input.hasArray() && output.hasArray()) {
                return encodeArrays(input, output, endOfInput);
            } else {
                return encodeBuffers(input, output, endOfInput);
            }
        } else {
            return CoderResult.UNDERFLOW;
        }
    }

    /**
     * The core encoding loop used when both the input and output buffers
     * are array backed.  The loop is expected to fetch the arrays and
     * interact with the arrays directly for performance.
     *
     * @param input the input buffer.
     * @param output the output buffer.
     * @param endOfInput when true, this is the last input to encode
     * @return UNDERFLOW or OVERFLOW
     */
    CoderResult encodeArrays(CharBuffer input, CharBuffer output, boolean endOfInput) {
        throw new UnsupportedOperationException();
    }

    /**
     * The core encoding loop used when either or both input and output
     * buffers are NOT array-backed.  E.g. they are direct buffers or
     * perhaps the input buffer is a read-only wrapper.  In any case,
     * this method is not currently implemented by any of the encoder
     * implementations since it is not expected to be common use-case.
     * The stub is included here for completeness and to demarcate
     * where the non-array-backed use-case would be included.
     *
     * @param input the input buffer.
     * @param output the output buffer.
     * @param endOfInput when true, this is the last input to encode
     * @return never returns.
     * @throws UnsupportedOperationException -- always
     */
    CoderResult encodeBuffers(CharBuffer input, CharBuffer output, boolean endOfInput)
        throws UnsupportedOperationException
    {
        throw new UnsupportedOperationException();
    }

    /**
     * Returns the maximum encoded length (in chars) of an input sequence of
     * {@code n} characters.
     *
     * @param n the number of characters of input
     * @return the worst-case number of characters required to encode
     */
    abstract int maxEncodedLength(int n);

    /**
     * Scans the input string for the first character index that requires
     * encoding.  If the entire input does not require encoding then the
     * length is returned.  This method is used by the Encode.forXYZ methods
     * to return input strings unchanged when possible.
     *
     * @param input the input to check for encoding
     * @param off the offset of the first character to check
     * @param len the number of characters to check
     * @return the index of the first character to encode.  The return value
     * will be {@code off+len} if no characters in the input require encoding.
     */
    abstract int firstEncodedOffset(String input, int off, int len);

    /**
     * Internal helper method to properly position buffers after encoding up
     * until an overflow.
     *
     * @param input the input buffer
     * @param i the array offset in the input buffer (translated to position)
     * @param output the output buffer
     * @param j the array offset in the output buffer (translated to position)
     * @return CoderResult.OVERFLOW
     */
    static CoderResult overflow(CharBuffer input, int i, CharBuffer output, int j) {
        input.position(i - input.arrayOffset());
        output.position(j - output.arrayOffset());
        return CoderResult.OVERFLOW;
    }

    /**
     * Internal helper method to properly position buffers after encoding up
     * until an underflow.
     *
     * @param input the input buffer
     * @param i the array offset in the input buffer (translated to position)
     * @param output the output buffer
     * @param j the array offset in the output buffer (translated to position)
     * @return CoderResult.UNDERFLOW
     */
    static CoderResult underflow(CharBuffer input, int i, CharBuffer output, int j) {
        input.position(i - input.arrayOffset());
        output.position(j - output.arrayOffset());
        return CoderResult.UNDERFLOW;
    }
}


// Copyright (c) 2012 Jeff Ichnowski
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//     * Redistributions of source code must retain the above
//       copyright notice, this list of conditions and the following
//       disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials
//       provided with the distribution.
//
//     * Neither the name of the OWASP nor the names of its
//       contributors may be used to endorse or promote products
//       derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package org.owasp.encoder;

/**
 * ASCIIBits - Small helper class for building up 128-bit bit-mask (2 longs)
 * to be used testing lower-ASCII characters.  It helps make some other code
 * easier to read.  It is not intended to be public.
 */
class ASCIIBits {
    /** Lower 64 bits. */
    long _lowerMask;
    /** Upper 64 bits. */
    long _upperMask;

    /**
     * Sets a bit to 1 for each character in the argument string.  No checking
     * is performed to see if characters are in the valid range 0..127.
     *
     * @param chars the characters to set to 1.
     * @return {@code this}
     */
    ASCIIBits set(String chars) {
        for (int i=0, n=chars.length() ; i<n ; ++i) {
            char ch = chars.charAt(i);
            if (ch < 64) {
                _lowerMask |= (1L << ch);
            } else {
                _upperMask |= (1L << ch);
            }
        }
        return this;
    }

    /**
     * Clears the bit (sets to 0) for each character in the argument string.
     *
     * @param chars the characters to clear.
     * @return {@code this}
     */
    ASCIIBits clear(String chars) {
        for (int i=0, n=chars.length() ; i<n ; ++i) {
            char ch = chars.charAt(i);
            if (ch < 64) {
                _lowerMask &= ~(1L << ch);
            } else {
                _upperMask &= ~(1L << ch);
            }
        }
        return this;
    }

    /**
     * Sets a range of characters to 1s in the masks.
     *
     * @param min the minimum (inclusive).
     * @param max the maximum (inclusive).
     * @return {@code this}
     */
    ASCIIBits set(int min, int max) {
        // There are faster bit-twiddling tricks to accomplish the same
        // thing as below.  Minor optimization for later.
        int i=min;
        for (int n = Math.min(max,63) ; i<=n ; ++i) {
            _lowerMask |= (1L << i);
        }
        for ( ; i<=max ; ++i) {
            _upperMask |= (1L << i);
        }
        return this;
    }

    /**
     * Sets a range of characters to 0s in the masks.
     *
     * @param min the minimum (inclusive).
     * @param max the maximum (inclusive).
     * @return {@code this}
     */
    ASCIIBits clear(char min, char max) {
        // There are faster bit-twiddling tricks to accomplish the same
        // thing as below.  Minor optimization for later.
        int i=min;
        for (int n = Math.min(max,63) ; i<=n ; ++i) {
            _lowerMask &= ~(1L << i);
        }
        for ( ; i<=max ; ++i) {
            _upperMask &= ~(1L << i);
        }
        return this;
    }
}


// Copyright (c) 2012 Jeff Ichnowski
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//     * Redistributions of source code must retain the above
//       copyright notice, this list of conditions and the following
//       disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials
//       provided with the distribution.
//
//     * Neither the name of the OWASP nor the names of its
//       contributors may be used to endorse or promote products
//       derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
package org.owasp.encoder;

import java.nio.CharBuffer;
import java.nio.charset.CoderResult;

/**
 * XMLEncoder -- encoder for XML attribute and content data. It uses XML entity
 * entity ("&...;") to encode valid but significant characters. Characters
 * that are invalid according to the XML specification are replaced by a space
 * character (U+0020). This encoder supports several modes of operation,
 * allowing for varying contexts, such as: attribute data between single-quotes,
 * attribute data between double-quotes, attribute data with indeterminate
 * quotes, content, or a context safe for all of the above.
 *
 * @author jeffi
 */
class XMLEncoder extends Encoder {

    // [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
    // Unicode Noncharacters (Unicode Standard 16.7)
    //  U+FFFE &  U+FFFF
    // U+1FFFE & U+1FFFF
    // U+2FFFE & U+2FFFF
    // ...
    // U+10FFFE & U+10FFFF
    // U+FDD0 .. U+FDEF
    // Control Characters
    // U+0000 .. U+001F <-- CR, LF, TAB are in this range and ok.
    // U+007f .. U+009F <-- U+85 = NEL (next line) = CR+LF in one = ok.
    // Note: the standard says it is a good practice to replace noncharacters
    // with U+FFFD "replacement character".
    /**
     * A bit-mask of valid characters with code-points in the range 0--63.
     */
    private static final long BASE_VALID_MASK
            = (1L << '\t') | (1L << '\r') | (1L << '\n');

    /**
     * Maximum number of encoded characters per input character.
     */
    static final int MAX_ENCODED_CHAR_LENGTH = 5;
    /**
     * The encoded length of an ampersand.
     */
    static final int AMP_LENGTH = 5;
    /**
     * The encoded length of a less-than sign.
     */
    static final int LT_LENGTH = 4;
    /**
     * The encoded length of a greater-than sign.
     */
    static final int GT_LENGTH = 4;
    /**
     * The encoded length of an apostrophe.
     */
    static final int APOS_LENGTH = 5;
    /**
     * The encoded length of a double-quotation character.
     */
    static final int QUOT_LENGTH = 5;

    /**
     * An enum of supported "modes" of operation for the XMLEncoder.
     */
    enum Mode {

        /**
         * All significant characters are encoded (& < > ' "). This
         * mode is safe for use in either content or attributes. See note on
         * {@link #CONTENT} for explanation of why '>' is encoded.
         */
        ALL("&<>\'\""),
        /**
         * Characters are encoded for content (a.k.a. "CharData"). This means
         * & < and >. Note: > only requires encoding if it follows
         * "]]". However for maximum compatibility and to avoid the overhead of
         * looking for "]]", we just always encode '>' to '&gt;'.
         */
        CONTENT("&<>"),
        /**
         * Characters are encoded for attribute values--either single or double
         * quoted. This means the characters & &lt ' and " are encoded.
         * Note: > is NOT encoded, and thus this mode is not suitable for
         * content.
         */
        ATTRIBUTE("&<\'\""),
        /**
         * Characters are encoded for single-quoted attribute values. Thus, the
         * same as {@link #ATTRIBUTE} except ' is not encoded.
         */
        SINGLE_QUOTED_ATTRIBUTE("&<\'"),
        /**
         * Characters are encoded for double-quoted attribute values. Thus, the
         * same as {@link #ATTRIBUTE} except " is not encoded.
         */
        DOUBLE_QUOTED_ATTRIBUTE("&<\""),;

        /**
         * The bit-mask of characters that do not need encoding in this mode.
         */
        private final long _validMask;

        /**
         * Sole constructor.
         *
         * @param encodedChars -- a string of characters must be encoded in this
         * mode. This string is converted to a bit-mask.
         */
        Mode(String encodedChars) {
            long encodeMask = 0;
            for (int i = 0, n = encodedChars.length(); i < n; ++i) {
                encodeMask |= 1L << encodedChars.charAt(i);
            }
            _validMask = BASE_VALID_MASK | ((-1L << ' ') & ~(encodeMask));
        }

        /**
         * Accessor for {@link #_validMask}.
         *
         * @return {@link #_validMask}
         */
        long validMask() {
            return _validMask;
        }
    }

    /**
     * Character to use as a replacement for invalid characters (Not to be
     * confused with characters that require encoding). Invalid characters have
     * no encoding, and are not allowed in the context.
     */
    static final char INVALID_CHARACTER_REPLACEMENT = ' ';

    /**
     * The mask of valid characters extracted from the mode for efficiency.
     */
    private final long _validMask;
    /**
     * The mode of operation--only really stored to provide a relevant toString
     * implementation.
     */
    private final Mode _mode;

    /**
     * Default constructor--equivalent to XMLEncoder(Mode.ALL).
     */
    XMLEncoder() {
        this(Mode.ALL);
    }

    /**
     * Creates an XMLEncoder for the specified mode constant.
     *
     * @param mode the mode of the encoder.
     */
    XMLEncoder(Mode mode) {
        _mode = mode;
        _validMask = mode.validMask();
    }

    @Override
    public int maxEncodedLength(int n) {
        // "&" = 5 chars.
        return n * MAX_ENCODED_CHAR_LENGTH;
    }

    @Override
    public int firstEncodedOffset(String input, int off, int len) {
        final int n = off + len;

        for (int i = off; i < n; ++i) {
            char ch = input.charAt(i);
            if (ch < Unicode.DEL) {
                if (ch <= '>' && (_validMask & (1L << ch)) == 0) {
                    // either needs encoding or is invalid
                    return i;
//                } else {
//                    // valid
                }
            } else if (ch < Character.MIN_HIGH_SURROGATE) {
                if (ch <= Unicode.MAX_C1_CTRL_CHAR && ch != Unicode.NEL) {
                    return i;
//                } else {
//                    // valid
                }
            } else if (ch <= Character.MAX_HIGH_SURROGATE) {
                if (i + 1 < n && Character.isLowSurrogate(input.charAt(i + 1))) {
                    int cp = Character.toCodePoint(ch, input.charAt(i + 1));
                    if (Unicode.isNonCharacter(cp)) {
                        // noncharacter
                        return i;
                    }
                    ++i;
                } else {
                    return i;
                }
            } else if (ch <= Character.MAX_LOW_SURROGATE
                    || ch > '\ufffd'
                    || ('\ufdd0' <= ch && ch <= '\ufdef'))
            {
                return i;
//            } else {
//                // valid
            }
        }

        return n;
    }

    /**
     * {@inheritDoc}
     */
    protected CoderResult encodeArrays(CharBuffer input, CharBuffer output, boolean endOfInput) {
        final char[] in = input.array();
        final char[] out = output.array();
        int i = input.arrayOffset() + input.position();
        final int n = input.arrayOffset() + input.limit();
        int j = output.arrayOffset() + output.position();
        final int m = output.arrayOffset() + output.limit();

        for (; i < n; ++i) {
            final char ch = in[i];
            if (ch < Unicode.DEL) {
                if (ch > '>' || ((_validMask & (1L << ch)) != 0)) {
                    // Common case ('>' .. '~') reached in two branches
                    if (j >= m) {
                        return overflow(input, i, output, j);
                    }
                    out[j++] = ch;
                } else {
                    switch (ch) {
                        case '&':
                            if (j + AMP_LENGTH > m) {
                                return overflow(input, i, output, j);
                            }
                            out[j++] = '&';
                            out[j++] = 'a';
                            out[j++] = 'm';
                            out[j++] = 'p';
                            out[j++] = ';';
                            break;
                        case '<':
                            if (j + LT_LENGTH > m) {
                                return overflow(input, i, output, j);
                            }
                            out[j++] = '&';
                            out[j++] = 'l';
                            out[j++] = 't';
                            out[j++] = ';';
                            break;
                        case '>':
                            if (j + GT_LENGTH > m) {
                                return overflow(input, i, output, j);
                            }
                            out[j++] = '&';
                            out[j++] = 'g';
                            out[j++] = 't';
                            out[j++] = ';';
                            break;
                        case '\'':
                            // ' is valid in XML, but not in HTML, and numeric code is shorter
                            if (j + APOS_LENGTH > m) {
                                return overflow(input, i, output, j);
                            }
                            out[j++] = '&';
                            out[j++] = '#';
                            out[j++] = '3';
                            out[j++] = '9';
                            out[j++] = ';';
                            break;
                        case '\"':
                            // " is valid in XML and HTML, but numeric code is shorter
                            if (j + QUOT_LENGTH > m) {
                                return overflow(input, i, output, j);
                            }
                            out[j++] = '&';
                            out[j++] = '#';
                            out[j++] = '3';
                            out[j++] = '4';
                            out[j++] = ';';
                            break;
                        default:
                            // invalid character
                            if (j >= m) {
                                return overflow(input, i, output, j);
                            }
                            out[j++] = INVALID_CHARACTER_REPLACEMENT;
                            break;
                    }
                }
            } else if (ch < Character.MIN_HIGH_SURROGATE) {
                if (j >= m) {
                    return overflow(input, i, output, j);
                }
                if (ch > Unicode.MAX_C1_CTRL_CHAR || ch == Unicode.NEL) {
                    out[j++] = ch;
                } else {
                    // C1 control code
                    out[j++] = INVALID_CHARACTER_REPLACEMENT;
                }
            } else if (ch <= Character.MAX_HIGH_SURROGATE) {
                if (i + 1 < n) {
                    if (Character.isLowSurrogate(in[i + 1])) {
                        int cp = Character.toCodePoint(ch, in[i + 1]);
                        if (Unicode.isNonCharacter(cp)) {
                            // noncharacter
                            if (j >= m) {
                                return overflow(input, i, output, j);
                            }
                            out[j++] = INVALID_CHARACTER_REPLACEMENT;
                            ++i;
                        } else {
                            if (j + 1 >= m) {
                                return overflow(input, i, output, j);
                            }
                            out[j++] = ch;
                            out[j++] = in[++i];
                        }
                    } else {
                        // high without low
                        if (j >= m) {
                            return overflow(input, i, output, j);
                        }
                        out[j++] = INVALID_CHARACTER_REPLACEMENT;
                    }
                } else if (endOfInput) {
                    // end of input, high without low = invalid
                    if (j >= m) {
                        return overflow(input, i, output, j);
                    }
                    out[j++] = INVALID_CHARACTER_REPLACEMENT;
                } else {
                    break;
                }
            } else if (// low surrogate without preceding high surrogate
                    ch <= Character.MAX_LOW_SURROGATE
                    // or non-characters
                    || ch > '\ufffd'
                    || ('\ufdd0' <= ch && ch <= '\ufdef'))
            {
                if (j >= m) {
                    return overflow(input, i, output, j);
                }
                out[j++] = INVALID_CHARACTER_REPLACEMENT;
            } else {
                if (j >= m) {
                    return overflow(input, i, output, j);
                }
                out[j++] = ch;
            }
        }

        return underflow(input, i, output, j);
    }

    @Override
    public String toString() {
        return "XMLEncoder(" + _mode + ")";
    }
}


// Copyright (c) 2012 Jeff Ichnowski
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//     * Redistributions of source code must retain the above
//       copyright notice, this list of conditions and the following
//       disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials
//       provided with the distribution.
//
//     * Neither the name of the OWASP nor the names of its
//       contributors may be used to endorse or promote products
//       derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package org.owasp.encoder;

import java.io.IOException;
import java.io.Writer;
import java.nio.CharBuffer;
import java.nio.charset.CoderResult;

/**
 * Encode -- fluent interface for contextual encoding.  Example usage in a JSP:
 *
 *

 *     <input value="<%=Encode.forHtml(value)%>" />
 *

 *
 *
There are two versions of each contextual encoding method.  The first
 * takes a {@code String} argument and returns the encoded version as a
 * {@code String}.  The second version writes the encoded version directly
 * to a {@code Writer}.

 *
 *
Please make sure to read and understand the context that the method encodes
 * for.  Encoding for the incorrect context will likely lead to exposing a
 * cross-site scripting vulnerability.

 *
 * @author Jeff Ichnowski
 */
public final class Encode {
    /** No instances. */
    private Encode() {}

    /**
     *
Encodes for (X)HTML text content and text attributes.  Since
     * this method encodes for both contexts, it may be slightly less
     * efficient to use this method over the methods targeted towards
     * the specific contexts ({@link #forHtmlAttribute(String)} and
     * {@link #forHtmlContent(String)}.  In general this method should
     * be preferred unless you are really concerned with saving a few
     * bytes or are writing a framework that utilizes this
     * package.

     *
     * Example JSP Usage
     *

     *     <div><%=Encode.forHtml(unsafeData)%></div>
     *
     *     <input value="<%=Encode.forHtml(unsafeData)%>" />
     *

     *
     *

     *
Encoding Table

     *

     *

     *       Input
     *       Result
     *

     *

     *

     *

     *       {@code &}
     *       {@code &}
     *

     *

     *       {@code <}
     *       {@code <}
     *

     *

     *       {@code >}
     *       {@code >}
     *

     *

     *       {@code "}
     *       {@code "}
     *

     *

     *       {@code '}
     *       {@code '}
     *

     *

     *

     *
     *
Additional Notes

     *

     *
The encoding of the greater-than sign ({@code >}) is not
     * strictly required, but is included for maximum
     * compatibility.

     *
     *
Numeric encoding is used for double-quote character ({@code
     * "}) as it shorter than the also valid {@code "}.

     *
     *
Carriage return (U+0D), line-feed (U+0A), horizontal tab
     * (U+09) and space (U+20) are valid in quoted attributes and in
     * block in an unescaped form.

     *
     *
Surrogate pairs are passed through only if valid.

     *
     *
Characters that are not valid according
     * to the XML specification are replaced by a space character
     * as they could lead to parsing errors.  In particular only {@code #x9
     * | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
     * [#x10000-#x10FFFF]} are considered valid.

     *

     *
     * @param input the data to encode
     * @return the data encoded for html.
     */
    public static String forHtml(String input) {
        return forXml(input);
    }

    /**
     * See {@link #forHtml(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forHtml(Writer out, String input) throws IOException {
        forXml(out, input);
    }

    /**
     *
This method encodes for HTML text content.  It does not escape
     * quotation characters and is thus unsafe for use with
     * HTML attributes.  Use either forHtml or forHtmlAttribute for those
     * methods.

     *
     * Example JSP Usage
     *

     *     <div><%=Encode.forHtmlContent(unsafeData)%></div>
     *

     *

     *
Encoding Table

     *

     *

     *       Input
     *       Result
     *

     *

     *

     *

     *       {@code &}
     *       {@code &}
     *

     *

     *       {@code <}
     *       {@code <}
     *

     *

     *       {@code >}
     *       {@code >}
     *

     *

     *

     *
     *
Additional Notes

     *

     *
Single-quote character ({@code '}) and double-quote
     * character ({@code "}) do not require encoding in HTML
     * blocks, unlike other HTML contexts.

     *
     *
The encoding of the greater-than sign ({@code >}) is not
     * strictly required, but is included for maximum
     * compatibility.

     *
     *
Carriage return (U+0D), line-feed (U+0A), horizontal tab
     * (U+09) and space (U+20) are valid in quoted attributes and in
     * block in an unescaped form.

     *
     *
Surrogate pairs are passed through only if valid.

     *
     *
Characters that are not valid according
     * to the XML specification are replaced by a space character
     * as they could lead to parsing errors.  In particular only {@code #x9
     * | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
     * [#x10000-#x10FFFF]} are considered valid.

     *

     *
     * @param input the input to encode
     * @return the encoded result
     */
    public static String forHtmlContent(String input) {
        return forXmlContent(input);
    }

    /**
     * See {@link #forHtmlContent(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forHtmlContent(Writer out, String input)
        throws IOException
    {
        forXmlContent(out, input);
    }

    /**
     *
This method encodes for HTML text attributes.

     *
     * Example JSP Usage
     *

     *     <div><%=Encode.forHtmlAttribute(unsafeData)%></div>
     *

     *
     *

     *
Encoding Table

     *

     *

     *       Input
     *       Result
     *

     *

     *

     *

     *       {@code &}
     *       {@code &}
     *

     *

     *       {@code <}
     *       {@code <}
     *

     *

     *       {@code "}
     *       {@code "}
     *

     *

     *       {@code '}
     *       {@code '}
     *

     *

     *

     *
     *
Additional Notes

     *

     *
Both the single-quote character ({@code '}) and the
     * double-quote character ({@code "}) are encoded so this is safe
     * for HTML attributes with either enclosing character.

     *
     *
The encoding of the greater-than sign ({@code >}) is not
     * required for attributes.

     *
     *
Numeric encoding is used for double-quote character ({@code
     * "}) as it shorter than the also valid {@code "}.

     *
     *
Carriage return (U+0D), line-feed (U+0A), horizontal tab
     * (U+09) and space (U+20) are valid in quoted attributes and in
     * block in an unescaped form.

     *
     *
Surrogate pairs are passed through only if valid.

     *
     *
Characters that are not valid according
     * to the XML specification are replaced by a space character
     * as they could lead to parsing errors.  In particular only {@code #x9
     * | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
     * [#x10000-#x10FFFF]} are considered valid.

     *

     *
     * @param input the input to encode
     * @return the encoded result
     */
    public static String forHtmlAttribute(String input) {
        return forXmlAttribute(input);
    }

    /**
     * See {@link #forHtmlAttribute(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forHtmlAttribute(Writer out, String input)
        throws IOException
    {
        forXmlAttribute(out, input);
    }

    /**
     *
Encodes for unquoted HTML attribute values.  {@link
     * #forHtml(String)} or {@link #forHtmlAttribute(String)} should
     * usually be preferred over this method as quoted attributes are
     * XHTML compliant.

     *
     *
When using this method, the caller is not required to
     * provide quotes around the attribute (since it is encoded for
     * such context).  The caller should make sure that the attribute
     * value does not abut unsafe characters--and thus should usually
     * err on the side of including a space character after the
     * value.

     *
     *
Use of this method is discouraged as quoted attributes are
     * generally more compatible and safer.  Also note, that no
     * attempt has been made to optimize this encoding, though it is
     * still probably faster than other encoding libraries.

     *
     * Example JSP Usage
     *

     *     <input value=<%=Encode.forHtmlUnquotedAttribute(input)%> >
     *

     *
     *

     *
Encoding Table

     *

     *

     *       Input
     *       Result
     *

     *

     *

     *

     *         {@code U+0009} (horizontal tab)
     *         {@code 	}

     *

     *         {@code U+000A} (line feed)
     *         {@code
}

     *

     *         {@code U+000C} (form feed)
     *         {@code �}

     *

     *         {@code U+000D} (carriage return)
     *         {@code
}

     *

     *         {@code U+0020} (space)
     *         {@code  }

     *

     *         {@code &}
     *         {@code &}

     *

     *         {@code <}
     *         {@code <}

     *

     *         {@code >}
     *         {@code >}

     *

     *         {@code "}
     *         {@code "}

     *

     *         {@code '}
     *         {@code '}

     *

     *         {@code /}
     *         {@code /}

     *

     *         {@code =}
     *         {@code =}

     *

     *         {@code `}
     *         {@code `}

     *

     *         {@code U+0085} (next line)
     *         {@code …}

     *

     *         {@code U+2028} (line separator)
     *         {@code
}

     *

     *         {@code U+2029} (paragraph separator)
     *         {@code
}

     *

     *

     *
     *
Additional Notes

     *

     *
The following characters are not encoded:
     * {@code 0-9, a-z, A-Z}, {@code !}, {@code
     * #}, {@code $}, {@code %},
     * {@code (}, {@code )}, {@code
     * *}, {@code +}, {@code ,},
     * {@code -}, {@code .}, {@code
     * [}, {@code \}, {@code ]},
     * {@code ^}, {@code _}, {@code
     * }}.

     *
     *
Surrogate pairs are passed through only if valid.  Invalid
     * surrogate pairs are replaced by a hyphen (-).

     *
     *
Characters in the C0 and C1 control blocks and not
     * otherwise listed above are considered invalid and replaced by a
     * hyphen (-) character.

     *
     *
Unicode "non-characters" are replaced by hyphens (-).

     *

     *
     * @param input the attribute value to be encoded.
     * @return the attribute value encoded for unquoted attribute
     * context.
     */
    public static String forHtmlUnquotedAttribute(String input) {
        return encode(Encoders.HTML_UNQUOTED_ATTRIBUTE_ENCODER, input);
    }

    /**
     * See {@link #forHtmlUnquotedAttribute(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forHtmlUnquotedAttribute(Writer out, String input)
        throws IOException
    {
        encode(Encoders.HTML_UNQUOTED_ATTRIBUTE_ENCODER, out, input);
    }

    // HTML comment encoding is not currently supported because
    // of the number of vendor-specific sequences that would need
    // to be handled (e.g. ""

//    public static String forHtmlComment(String input) {
//        // only alphanumeric and space, everything else becomes a space
//
//        // HTML comment context needs to avoid browser extensions
//        // such as ""
//        throw new UnsupportedOperationException();
//    }

    /**
     * Encodes for CSS strings.  The context must be surrounded by quotation
     * characters.  It is safe for use in both style blocks and attributes in
     * HTML.
     *
     * Example JSP Usage
     *

     *     <div style="background: url('<=Encode.forCssString(...)%>');">
     *
     *     <style type="text/css">
     *         background: url('<%=Encode.forCssString(...)%>');
     *     </style>
     *

     *
     * Encoding  Notes
     *

     *
     *
The following characters are encoded using hexidecimal
     * encodings: {@code U+0000} - {@code U+001f},
     * {@code "},
     * {@code '},
     * {@code \},
     * {@code <},
     * {@code &},
     * {@code (},
     * {@code )},
     * {@code /},
     * {@code >},
     * {@code U+007f},
     * line separator ({@code U+2028}),
     * paragraph separator ({@code U+2029}).

     *
     *
Any character requiring encoding is encoded as {@code \xxx}
     * where {@code xxx} is the shortest hexidecimal representation of
     * its Unicode code point (after decoding surrogate pairs if
     * necessary).  This encoding is never zero padded.  Thus, for
     * example, the tab character is encoded as {@code \9}, not {@code
     * \0009}.

     *
     *
The encoder looks ahead 1 character in the input and
     * appends a space to an encoding to avoid the next character
     * becoming part of the hexidecimal encoded sequence.  Thus
     * “{@code '1}” is encoded as “{@code \27
     * 1}”, and not as “{@code \271}”.  If a space
     * is not necessary, it is not included, thus “{@code
     * 'x}” is encoded as “{@code \27x}”, and not as
     * “{@code \27 x}”.

     *
     *
Surrogate pairs are passed through only if valid.  Invalid
     * surrogate pairs are replaced by an underscore (_).

     *
     *
Unicode "non-characters" are replaced by underscores (_).

     *
     *

     *
     * @param input the input to encode
     * @return the encoded result
     */
    public static String forCssString(String input) {
        // need to watch out for CSS expressions
        return encode(Encoders.CSS_STRING_ENCODER, input);
    }

    /**
     * See {@link #forCssString(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forCssString(Writer out, String input)
        throws IOException
    {
        encode(Encoders.CSS_STRING_ENCODER, out, input);
    }

    /**
     * Encodes for CSS URL contexts.  The context must be surrounded by {@code "url("}
     * and {@code ")"}.  It is safe for use in both style blocks and attributes in HTML.
     * Note: this does not do any checking on the quality or safety of the URL
     * itself.  The caller should insure that the URL is safe for embedding
     * (e.g. input validation) by other means.
     *
     * Example JSP Usage
     *

     *     <div style="background:url(<=Encode.forCssUrl(...)%>);">
     *
     *     <style type="text/css">
     *         background: url(<%=Encode.forCssUrl(...)%>);
     *     </style>
     *

     * Encoding  Notes
     *

     *
     *
The following characters are encoded using hexidecimal
     * encodings: {@code U+0000} - {@code U+001f},
     * {@code "},
     * {@code '},
     * {@code \},
     * {@code <},
     * {@code &},
     * {@code /},
     * {@code >},
     * {@code U+007f},
     * line separator ({@code U+2028}),
     * paragraph separator ({@code U+2029}).

     *
     *
Any character requiring encoding is encoded as {@code \xxx}
     * where {@code xxx} is the shortest hexidecimal representation of
     * its Unicode code point (after decoding surrogate pairs if
     * necessary).  This encoding is never zero padded.  Thus, for
     * example, the tab character is encoded as {@code \9}, not {@code
     * \0009}.

     *
     *
The encoder looks ahead 1 character in the input and
     * appends a space to an encoding to avoid the next character
     * becoming part of the hexidecimal encoded sequence.  Thus
     * “{@code '1}” is encoded as “{@code \27
     * 1}”, and not as “{@code \271}”.  If a space
     * is not necessary, it is not included, thus “{@code
     * 'x}” is encoded as “{@code \27x}”, and not as
     * “{@code \27 x}”.

     *
     *
Surrogate pairs are passed through only if valid.  Invalid
     * surrogate pairs are replaced by an underscore (_).

     *
     *
Unicode "non-characters" are replaced by underscores (_).

     *
     *

     *
     * @param input the input to encode
     * @return the encoded result
     */
    public static String forCssUrl(String input) {
        return encode(Encoders.CSS_URL_ENCODER, input);
    }

    /**
     * See {@link #forCssUrl(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forCssUrl(Writer out, String input)
        throws IOException
    {
        encode(Encoders.CSS_URL_ENCODER, out, input);
    }

    /**
     *
Performs percent-encoding of a URL according to RFC 3986.  The provided
     * URL is assumed to a valid URL.  This method does not do any checking on
     * the quality or safety of the URL itself.  In many applications it may
     * be better to use {@link java.net.URI} instead.  Note: this is a
     * particularly dangerous context to put untrusted content in, as for
     * example a "javascript:" URL provided by a malicious user would be
     * "properly" escaped, and still execute.

     *
     * Encoding Table
     *
The following characters are not encoded:

     *

     * U+20:   !   # $   & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ;   =   ?
     * U+40: @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [   ]   _
     * U+60:   a b c d e f g h i j k l m n o p q r s t u v w x y z       ~
     *

     *
     * Encoding Notes
     *

     *
     *
The single-quote character({@code '}) is not encoded.

     *
     *
This encoding is not intended to be used standalone.  The
     *   output should be encoded to the target context.  For example:
     *   {@code <a
     *   href="<%=Encode.forHtmlAttribute(Encode.forUri(uri))%>">...}.
     *   (Note, the single-quote character ({@code '}) is not
     *   encoded.)

     *
     *
URL encoding is an encoding for bytes, not unicode.  The
     *   input string is thus first encoded as a sequence of UTF-8
     *   byte.  The bytes are then encoded as {@code %xx} where {@code
     *   xx} is the two-digit hexidecimal representation of the
     *   byte. (The implementation does this as one step for
     *   performance.)

     *
     *
Surrogate pairs are first decoded to a Unicode code point
     *   before encoding as UTF-8.

     *
     *
Invalid characters (e.g. partial or invalid surrogate
     *   pairs), are replaced with a hyphen ({@code -}) character.

     *
     *

     *
     * @param input the input to encode
     * @return the encoded result
     */
    @Deprecated public static String forUri(String input) {
        return encode(Encoders.URI_ENCODER, input);
    }

    /**
     * See {@link #forUri(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     *
     * @deprecated  There is never a need to encode a complete URI with this form of encoding.
     */
    @Deprecated public static void forUri(Writer out, String input)
        throws IOException
    {
        encode(Encoders.URI_ENCODER, out, input);
    }

    /**
     * Performs percent-encoding for a component of a URI, such as a query
     * parameter name or value, path or query-string.  In particular this
     * method insures that special characters in the component do not get
     * interpreted as part of another component.
     *
     *

     *     <a href="http://www.owasp.org/<%=Encode.forUriComponent(...)%>?query#fragment">
     *
     *     <a href="/search?value=<%=Encode.forUriComponent(...)%>&order=1#top">
     *

     *
     * Encoding Table
     *
The following characters are not encoded:

     *

     * U+20:                           - .   0 1 2 3 4 5 6 7 8 9
     * U+40: @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z         _
     * U+60:   a b c d e f g h i j k l m n o p q r s t u v w x y z       ~
     *

     *
     * Encoding Notes
     *

     *
     *
Unlike {@link #forUri(String)} this method is safe to be
     *   used in most containing contexts, including: HTML/XML, CSS,
     *   and JavaScript contexts.

     *
     *
URL encoding is an encoding for bytes, not unicode.  The
     *   input string is thus first encoded as a sequence of UTF-8
     *   byte.  The bytes are then encoded as {@code %xx} where {@code
     *   xx} is the two-digit hexidecimal representation of the
     *   byte. (The implementation does this as one step for
     *   performance.)

     *
     *
Surrogate pairs are first decoded to a Unicode code point
     *   before encoding as UTF-8.

     *
     *
Invalid characters (e.g. partial or invalid surrogate
     *   pairs), are replaced with a hyphen ({@code -}) character.

     *
     *

     *
     * @param input the input to encode
     * @return the encoded result
     */
    public static String forUriComponent(String input) {
        return encode(Encoders.URI_COMPONENT_ENCODER, input);
    }

    /**
     * See {@link #forUriComponent(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forUriComponent(Writer out, String input)
        throws IOException
    {
        encode(Encoders.URI_COMPONENT_ENCODER, out, input);
    }

    /**
     * Encoder for XML and XHTML.  See {@link #forHtml(String)} for a
     * description of the encoding and context.
     *
     * @see #forHtml(String)
     * @param input the input to encode
     * @return the encoded result
     */
    public static String forXml(String input) {
        return encode(Encoders.XML_ENCODER, input);
    }

    /**
     * See {@link #forXml(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forXml(Writer out, String input)
        throws IOException
    {
        encode(Encoders.XML_ENCODER, out, input);
    }

    /**
     * Encoder for XML and XHTML text content.  See {@link
     * #forHtmlContent(String)} for description of encoding and
     * context.
     *
     * @see #forHtmlContent(String)
     * @param input the input to encode
     * @return the encoded result
     */
    public static String forXmlContent(String input) {
        return encode(Encoders.XML_CONTENT_ENCODER, input);
    }

    /**
     * See {@link #forXmlContent(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forXmlContent(Writer out, String input)
        throws IOException
    {
        encode(Encoders.XML_CONTENT_ENCODER, out, input);
    }

    /**
     * Encoder for XML and XHTML attribute content.  See {@link
     * #forHtmlAttribute(String)} for description of encoding and
     * context.
     *
     * @see #forHtmlAttribute(String)
     * @param input the input to encode
     * @return the encoded result
     */
    public static String forXmlAttribute(String input) {
        return encode(Encoders.XML_ATTRIBUTE_ENCODER, input);
    }

    /**
     * See {@link #forXmlAttribute(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forXmlAttribute(Writer out, String input)
        throws IOException
    {
        encode(Encoders.XML_ATTRIBUTE_ENCODER, out, input);
    }

    /**
     * Encoder for XML comments.  NOT FOR USE WITH
     * (X)HTML CONTEXTS.  (X)HTML comments may be interpreted by
     * browsers as something other than a comment, typically in vendor
     * specific extensions (e.g. {@code <--if[IE]-->}).
     * For (X)HTML it is recommend that unsafe content never be included
     * in a comment.
     *
     *
The caller must provide the comment start and end sequences.

     *
     *
This method replaces all invalid XML characters with spaces,
     * and replaces the "--" sequence (which is invalid in XML comments)
     * with "-~" (hyphen-tilde).  This encoding behavior may change
     * in future releases.  If the comments need to be decoded, the
     * caller will need to come up with their own encode/decode system.

     *
     *

     *     out.println("<?xml version='1.0'?>");
     *     out.println("<data>");
     *     out.println("<!-- "+Encode.forXmlComment(comment)+" -->");
     *     out.println("</data>");
     *

     *
     * @param input the input to encode
     * @return the encoded result
     */
    public static String forXmlComment(String input) {
        return encode(Encoders.XML_COMMENT_ENCODER, input);
    }

    /**
     * See {@link #forXmlComment(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forXmlComment(Writer out, String input)
        throws IOException
    {
        encode(Encoders.XML_COMMENT_ENCODER, out, input);
    }

    /**
     * Encodes data for an XML CDATA section.  On the chance that the input
     * contains a terminating {@code "]]>"}, it will be replaced by
     * {@code "]]>]]<![CDATA[>"}.
     * As with all XML contexts, characters that are invalid according to the
     * XML specification will be replaced by a space character.   Caller must
     * provide the CDATA section boundaries.
     *
     *

     *     <xml-data><![CDATA[<%=Encode.forCDATA(...)%>]]></xml-data>
     *

     *
     * @param input the input to encode
     * @return the encoded result
     */
    public static String forCDATA(String input) {
        return encode(Encoders.CDATA_ENCODER, input);
    }

    /**
     * See {@link #forCDATA(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forCDATA(Writer out, String input)
        throws IOException
    {
        encode(Encoders.CDATA_ENCODER, out, input);
    }

    /**
     * Encodes for a Java string.  This method will use "\b", "\t", "\r", "\f",
     * "\n", "\"", "\'", "\\", octal and unicode escapes.  Valid surrogate
     * pairing is not checked.   The caller must provide the enclosing quotation
     * characters.  This method is useful for when writing code generators and
     * outputting debug messages.
     *
     *

     *     out.println("public class Hello {");
     *     out.println("    public static void main(String[] args) {");
     *     out.println("        System.out.println(\"" + Encode.forJava(message) + "\");");
     *     out.println("    }");
     *     out.println("}");
     *

     *
     * @param input the input to encode
     * @return the input encoded for java strings.
     */
    public static String forJava(String input) {
        return encode(Encoders.JAVA_ENCODER, input);
    }

    /**
     * See {@link #forJava(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forJava(Writer out, String input)
        throws IOException
    {
        encode(Encoders.JAVA_ENCODER, out, input);
    }

    /**
     *
Encodes for a JavaScript string.  It is safe for use in HTML
     * script attributes (such as {@code onclick}), script
     * blocks, JSON files, and JavaScript source.  The caller MUST
     * provide the surrounding quotation characters for the string.
     * Since this performs additional encoding so it can work in all
     * of the JavaScript contexts listed, it may be slightly less
     * efficient than using one of the methods targetted to a specific
     * JavaScript context ({@link #forJavaScriptAttribute(String)},
     * {@link #forJavaScriptBlock}, {@link #forJavaScriptSource}).
     * Unless you are interested in saving a few bytes of output or
     * are writing a framework on top of this library, it is recommend
     * that you use this method over the others.

     *
     * Example JSP Usage:
     *

     *    <button onclick="alert('<%=Encode.forJavaScript(data)%>');">
     *    <script type="text/javascript">
     *        var data = "<%=Encode.forJavaScript(data)%>";
     *    </script>
     *

     *
     *

     *
Encoding Description

     *

     *

     *       Input Character
     *       Encoded Result
     *       Notes
     *

     *

     *

     *

     *       U+0008	BS
     *       \b
     *       Backspace character
     *

     *

     *       U+0009	HT
     *       \t
     *       Horizontal tab character
     *

     *

     *       U+000A	LF
     *       \n
     *       Line feed character
     *

     *

     *       U+000C	FF
     *       \f
     *       Form feed character
     *

     *

     *       U+000D	CR
     *       \r
     *       Carriage return character
     *

     *

     *       U+0022	"
     *       \x22
     *       The encoding \" is not used here because
     *       it is not safe for use in HTML attributes.  (In HTML
     *       attributes, it would also be correct to use
     *       "\&quot;".)
     *

     *

     *       U+0026	&
     *       \x26
     *       Ampersand character
     *

     *

     *       U+0027	'
     *       \x27
     *       The encoding \' is not used here because
     *       it is not safe for use in HTML attributes.  (In HTML
     *       attributes, it would also be correct to use
     *       "\&#39;".)
     *

     *

     *       U+002F	/
     *       \/
     *       This encoding is used to avoid an input sequence
     *       "</" from prematurely terminating a </script>
     *       block.
     *

     *

     *       U+005C	\
     *       \\
     *
     *

     *

     *       U+0000 to U+001F
     *       \x##
     *       Hexadecimal encoding is used for characters in this
     *       range that were not already mentioned in above.
     *

     *

     *

     *
     * @param input the input string to encode
     * @return the input encoded for JavaScript
     * @see #forJavaScriptAttribute(String)
     * @see #forJavaScriptBlock(String)
     */
    public static String forJavaScript(String input) {
        return encode(Encoders.JAVASCRIPT_ENCODER, input);
    }

    /**
     * See {@link #forJavaScript(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forJavaScript(Writer out, String input)
        throws IOException
    {
        encode(Encoders.JAVASCRIPT_ENCODER, out, input);
    }

    /**
     *
This method encodes for JavaScript strings contained within
     * HTML script attributes (such as {@code onclick}).  It is
     * NOT safe for use in script blocks.  The caller MUST provide the
     * surrounding quotation characters.  This method performs the
     * same encode as {@link #forJavaScript(String)} with the
     * exception that / is not escaped.

     *
     *
Unless you are interested in saving a few bytes of
     * output or are writing a framework on top of this library, it is
     * recommend that you use {@link #forJavaScript(String)} over this
     * method.

     *
     * Example JSP Usage:
     *

     *    <button onclick="alert('<%=Encode.forJavaScriptAttribute(data)%>');">
     *

     *
     * @param input the input string to encode
     * @return the input encoded for JavaScript
     * @see #forJavaScript(String)
     * @see #forJavaScriptBlock(String)
     */
    public static String forJavaScriptAttribute(String input) {
        return encode(Encoders.JAVASCRIPT_ATTRIBUTE_ENCODER, input);
    }

    /**
     * See {@link #forJavaScriptAttribute(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forJavaScriptAttribute(Writer out, String input)
        throws IOException
    {
        encode(Encoders.JAVASCRIPT_ATTRIBUTE_ENCODER, out, input);
    }

    /**
     *
This method encodes for JavaScript strings contained within
     * HTML script blocks.  It is NOT safe for use in script
     * attributes (such as onclick).  The caller must
     * provide the surrounding quotation characters.  This method
     * performs the same encode as {@link #forJavaScript(String)} with
     * the exception that " and ' are
     * encoded as \" and \'
     * respectively.

     *
     *
Unless you are interested in saving a few bytes of
     * output or are writing a framework on top of this library, it is
     * recommend that you use {@link #forJavaScript(String)} over this
     * method.

     *
     * Example JSP Usage:
     *

     *    <script type="text/javascript">
     *        var data = "<%=Encode.forJavaScriptBlock(data)%>";
     *    </script>
     *

     *
     * @param input the input string to encode
     * @return the input encoded for JavaScript
     * @see #forJavaScript(String)
     * @see #forJavaScriptAttribute(String)
     */
    public static String forJavaScriptBlock(String input) {
        return encode(Encoders.JAVASCRIPT_BLOCK_ENCODER, input);
    }

    /**
     * See {@link #forJavaScriptBlock(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forJavaScriptBlock(Writer out, String input)
        throws IOException
    {
        encode(Encoders.JAVASCRIPT_BLOCK_ENCODER, out, input);
    }

    /**
     *
This method encodes for JavaScript strings contained within
     * a JavaScript or JSON file.  This method is NOT safe for
     * use in ANY context embedded in HTML. The caller must
     * provide the surrounding quotation characters.  This method
     * performs the same encode as {@link #forJavaScript(String)} with
     * the exception that / and & are not
     * escaped and " and ' are encoded as
     * \" and \' respectively.

     *
     *
Unless you are interested in saving a few bytes of
     * output or are writing a framework on top of this library, it is
     * recommend that you use {@link #forJavaScript(String)} over this
     * method.

     *
     * Example JSP Usage:
     * This example is serving up JavaScript source directly:
     *

     *    <%@page contentType="text/javascript; charset=UTF-8"%>
     *    var data = "<%=Encode.forJavaScriptSource(data)%>";
     *

     *
     * This example is serving up JSON data (users of this use-case
     * are encouraged to read up on "JSON Hijacking"):
     *

     *    <%@page contentType="application/json; charset=UTF-8"%>
     *    <% myapp.jsonHijackingPreventionMeasure(); %>
     *    {"data":"<%=Encode.forJavaScriptSource(data)%>"}
     *

     *
     * @param input the input string to encode
     * @return the input encoded for JavaScript
     * @see #forJavaScript(String)
     * @see #forJavaScriptAttribute(String)
     * @see #forJavaScriptBlock(String)
     */
    public static String forJavaScriptSource(String input) {
        return encode(Encoders.JAVASCRIPT_SOURCE_ENCODER, input);
    }

    /**
     * See {@link #forJavaScriptSource(String)} for description of encoding.  This
     * version writes directly to a Writer without an intervening string.
     *
     * @param out where to write encoded output
     * @param input the input string to encode
     * @throws IOException if thrown by writer
     */
    public static void forJavaScriptSource(Writer out, String input)
        throws IOException
    {
        encode(Encoders.JAVASCRIPT_SOURCE_ENCODER, out, input);
    }

    // Additional?
    // MySQL
    // PostreSQL
    // Oracle
    // ...

    /**
     * Core encoding loop shared by public methods.  It first uses the
     * encoder to scan the input for characters that need encoding.  If
     * no characters require encoding, the input string is returned.
     * Otherwise a buffer is used to encode the remainder
     * of the input.
     *
     * @param encoder the encoder to use
     * @param str the string to encode
     * @return the input string encoded with the provided encoder.
     */
    static String encode(Encoder encoder, String str) {
        if (str == null) {
            // consistent with String.valueOf(...) use "null" for null.
            str = "null";
        }

        // quick pass--see if we need to actually encode anything, if not
        // return the value unchanged.
        final int n = str.length();
        int j = encoder.firstEncodedOffset(str, 0, n);

        if (j == n) {
            return str;
        }

        // otherwise, we need to encode.  We use a buffer to avoid
        // excessive memory allocation for these calls.  Note: this means that
        // an encoder implementation must NEVER call this method internally.
        return new Buffer().encode(encoder, str, j);
    }

    /**
     * Core encoding loop shared by public methods.  It first uses the
     * encoder to scan the input for characters that need encoding.  If no
     * characters require encoding, the input string is written directly to
     * the writer.  Otherwise a buffer is used to encode the
     * remainder of the input to the buffers.  This version saves a wrapping
     * in an String.
     *
     * @param encoder the encoder to use
     * @param out the writer for the encoded output
     * @param str the string to encode
     * @throws IOException if thrown by the writer
     */
    static void encode(Encoder encoder, Writer out, String str)
        throws IOException
    {
        if (str == null) {
            // consistent with String.valueOf(...) use "null" for null.
            str = "null";
        }

        // quick pass--see if we need to actually encode anything, if not
        // return the value unchanged.
        final int n = str.length();
        int j = encoder.firstEncodedOffset(str, 0, n);

        if (j == n) {
            out.write(str);
            return;
        }

        // otherwise, we need to encode.  We use a buffer to avoid
        // excessive memory allocation for these calls.  Note: this means that
        // an encoder implementation must NEVER call this method internally.
        new Buffer().encode(encoder, out, str, j);
    }

    /**
     * A buffer used for encoding.
     */
    static class Buffer {
        /**
         * Input buffer size, used to extract a copy of the input
         * from a string and then send to the encoder.
         */
        static final int INPUT_BUFFER_SIZE = 1024;
        /**
         * Output buffer size used to store the encoded output before
         * wrapping in a string.
         */
        static final int OUTPUT_BUFFER_SIZE = INPUT_BUFFER_SIZE * 2;

        /**
         * The input buffer.  A heap-allocated, array-backed buffer of
         * INPUT_BUFFER_SIZE used for holding the characters to encode.
         */
        final CharBuffer _input = CharBuffer.allocate(INPUT_BUFFER_SIZE);
        /**
         * The output buffer.  A heap-allocated, array-backed buffer of
         * OUTPUT_BUFFER_SIZE used for holding the encoded output.
         */
        final CharBuffer _output = CharBuffer.allocate(OUTPUT_BUFFER_SIZE);

        /**
         * The core String encoding routine of this class.  It uses the input
         * and output buffers to allow the encoders to work in reuse arrays.
         * When the input and/or output exceeds the capacity of the reused
         * arrays, temporary ones are allocated and then discarded after
         * the encode is done.
         *
         * @param encoder the encoder to use
         * @param str the string to encode
         * @param j the offset in {@code str} to start encoding
         * @return the encoded result
         */
        String encode(Encoder encoder, String str, int j) {
            final int n = str.length();
            final int remaining = n - j;

            if (remaining <= INPUT_BUFFER_SIZE && j <= OUTPUT_BUFFER_SIZE) {
                // the remaining input to encode fits completely in the pre-
                // allocated buffer.
                str.getChars(0, j, _output.array(), 0);
                str.getChars(j, n, _input.array(), 0);

                _input.limit(remaining).position(0);
                _output.clear().position(j);

                CoderResult cr = encoder.encodeArrays(_input, _output, true);
                if (cr.isUnderflow()) {
                    return new String(_output.array(), 0, _output.position());
                }

                // else, it's an overflow, we need to use a new output buffer
                // we'll allocate this buffer to be the exact size of the worst
                // case, guaranteeing a second overflow would not be possible.
                CharBuffer tmp = CharBuffer.allocate(_output.position()
                            + encoder.maxEncodedLength(_input.remaining()));

                // copy over everything that has been encoded so far
                tmp.put(_output.array(), 0, _output.position());

                cr = encoder.encodeArrays(_input, tmp, true);
                if (cr.isOverflow()) {
                    throw new AssertionError("unexpected result from encoder");
                }

                return new String(tmp.array(), 0, tmp.position());
            } else {
                // the input it too large for our pre-allocated buffers
                // we'll use a temporary direct heap allocation
                final int m = j + encoder.maxEncodedLength(remaining);
                CharBuffer buffer = CharBuffer.allocate(m);
                str.getChars(0, j, buffer.array(), 0);
                str.getChars(j, n, buffer.array(), m - remaining);

                CharBuffer input = buffer.duplicate();
                input.limit(m).position(m-remaining);
                buffer.position(j);

                CoderResult cr = encoder.encodeArrays(input, buffer, true);

                if (cr.isOverflow()) {
                    throw new AssertionError("unexpected result from encoder");
                }

                return new String(buffer.array(), 0, buffer.position());
            }
        }

        /**
         * The core Writer encoding routing of this class.  It uses the
         * input and output buffers to allow the encoders to reuse arrays.
         * Unlike the string version, this method will never allocate more
         * memory, instead encoding is done in batches and flushed to the
         * writer in batches as large as possible.
         *
         * @param encoder the encoder to use
         * @param out where to write the encoded output
         * @param str the string to encode
         * @param j the position in the string at which the first character
         * needs encoding.
         * @throws IOException if thrown by the writer.
         */
        void encode(Encoder encoder, Writer out, String str, int j)
            throws IOException
        {
            out.write(str, 0, j);

            final int n = str.length();

            _input.clear();
            _output.clear();

            final char[] inputArray = _input.array();
            final char[] outputArray = _output.array();

            for (;;) {
                final int remainingInput = n - j;
                final int startPosition = _input.position();
                final int batchSize = Math.min(remainingInput, _input.remaining());
                str.getChars(j, j+batchSize, inputArray, startPosition);

                _input.limit(startPosition + batchSize);

                for (;;) {
                    CoderResult cr = encoder.encodeArrays(
                        _input, _output, batchSize == remainingInput);

                    if (cr.isUnderflow()) {
                        // get next input batch
                        break;
                    }

                    // else, output buffer full, flush and continue.
                    out.write(outputArray, 0, _output.position());
                    _output.clear();
                }

                j += _input.position() - startPosition;

                if (j == n) {
                    // done.  flush remaining output buffer and return
                    out.write(outputArray, 0, _output.position());
                    return;
                }

                _input.compact();
            }
        }
    }
}


// Copyright (c) 2012 Jeff Ichnowski
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//     * Redistributions of source code must retain the above
//       copyright notice, this list of conditions and the following
//       disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials
//       provided with the distribution.
//
//     * Neither the name of the OWASP nor the names of its
//       contributors may be used to endorse or promote products
//       derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
package org.owasp.encoder;

import java.util.HashMap;
import java.util.Map;

/**
 * Encoders -- Public factory method for obtaining instances of Encoders.
 * Classes implementing the encoders are not directly exposed as part of the API
 * since encoding strategies are subject to change. In many cases encoders will
 * share the same implementation, but have different internal flags for how to
 * handle varied content. For example the XML_CONTENT and XML_ATTRIBUTE contexts
 * may currently share the same class with each instances having a different set
 * of flags. Future version may optimize them into different classes.
 *
 *

 * All encoders returned by the factory are thread-safe.

 *
 * @author Jeff Ichnowski
 */
public final class Encoders {

    /**
     * Name of {@linkplain Encode#forHtml(String) HTML general} context.
     */
    public static final String HTML = "html";
    /**
     * Name of {@linkplain Encode#forHtmlContent(String) HTML content} context.
     */
    public static final String HTML_CONTENT = "html-content";
    /**
     * Name of {@linkplain Encode#forHtmlAttribute(String) HTML attribute}
     * context.
     */
    public static final String HTML_ATTRIBUTE = "html-attribute";
    /**
     * Name of
     * {@linkplain Encode#forHtmlUnquotedAttribute(String) unquoted HTML attribute}
     * context.
     */
    public static final String HTML_UNQUOTED_ATTRIBUTE = "html-attribute-unquoted";
    /**
     * Name of {@linkplain Encode#forXml(String) XML general} context.
     */
    public static final String XML = "xml";
    /**
     * Name of {@linkplain Encode#forXmlContent(String) XML content} context.
     */
    public static final String XML_CONTENT = "xml-content";
    /**
     * Name of {@linkplain Encode#forXmlAttribute(String) XML attribute}
     * context.
     */
    public static final String XML_ATTRIBUTE = "xml-attribute";
    /**
     * Name of {@linkplain Encode#forXmlComment(String) XML comment} context.
     */
    public static final String XML_COMMENT = "xml-comment";
    /**
     * Name of {@linkplain Encode#forCDATA(String) CDATA} context.
     */
    public static final String CDATA = "cdata";
    /**
     * Name of {@linkplain Encode#forCssString(String) CSS string} context.
     */
    public static final String CSS_STRING = "css-string";
    /**
     * Name of {@linkplain Encode#forCssUrl(String) CSS URL} context.
     */
    public static final String CSS_URL = "css-url";
    /**
     * Name of {@linkplain Encode#forJava(String) Java String} context.
     */
    public static final String JAVA = "java";
    /**
     * Name of {@linkplain Encode#forJavaScript(String) JavaScript general}
     * context.
     */
    public static final String JAVASCRIPT = "javascript";
    /**
     * Name of
     * {@linkplain Encode#forJavaScriptAttribute(String) JavaScript attribute}
     * context.
     */
    public static final String JAVASCRIPT_ATTRIBUTE = "javascript-attribute";
    /**
     * Name of {@linkplain Encode#forJavaScriptBlock(String) JavaScript block}
     * context.
     */
    public static final String JAVASCRIPT_BLOCK = "javascript-block";
    /**
     * Name of {@linkplain Encode#forJavaScriptSource(String) JavaScript source}
     * context.
     */
    public static final String JAVASCRIPT_SOURCE = "javascript-source";
    /**
     * Name of {@linkplain Encode#forUri(String) URI} context.
     */
    public static final String URI = "uri";
    /**
     * Name of {@linkplain Encode#forUriComponent(String) URI component}
     * context.
     */
    public static final String URI_COMPONENT = "uri-component";
    /**
     * Map from encoder name to encoder singleton.
     */
    private static final Map<String, Encoder> ENCODERS_MAP
            = new HashMap<String, Encoder>(32);
    // XML and HTML use the same encoder implementations currently
    /**
     * Encoder for general XML/HTML contexts.
     */
    static final XMLEncoder XML_ENCODER
            = map(HTML, map(XML, new XMLEncoder(XMLEncoder.Mode.ALL)));
    /**
     * Encoder for XML/HTML content contexts.
     */
    static final XMLEncoder XML_CONTENT_ENCODER
            = map(HTML_CONTENT, map(XML_CONTENT, new XMLEncoder(XMLEncoder.Mode.CONTENT)));
    /**
     * Encoder for XML/HTML attribute contexts.
     */
    static final XMLEncoder XML_ATTRIBUTE_ENCODER
            = map(HTML_ATTRIBUTE, map(XML_ATTRIBUTE, new XMLEncoder(XMLEncoder.Mode.ATTRIBUTE)));
    /**
     * Encoder for XML comments.
     */
    static final XMLCommentEncoder XML_COMMENT_ENCODER
            = map(XML_COMMENT, new XMLCommentEncoder());
    /**
     * Encoder for CDATA contexts.
     */
    static final CDATAEncoder CDATA_ENCODER
            = map(CDATA, new CDATAEncoder());
    /**
     * Encoder for unquoted HTML attributes.
     */
    static final HTMLEncoder HTML_UNQUOTED_ATTRIBUTE_ENCODER
            = map(HTML_UNQUOTED_ATTRIBUTE, new HTMLEncoder());
    /**
     * Encoder for general JavaScript contexts.
     */
    static final JavaScriptEncoder JAVASCRIPT_ENCODER
            = map(JAVASCRIPT, new JavaScriptEncoder(JavaScriptEncoder.Mode.HTML, false));
    /**
     * Encoder for JavaScript appearing in XML/HTML attributes.
     */
    static final JavaScriptEncoder JAVASCRIPT_ATTRIBUTE_ENCODER
            = map(JAVASCRIPT_ATTRIBUTE, new JavaScriptEncoder(JavaScriptEncoder.Mode.ATTRIBUTE, false));
    /**
     * Encoder for JavaScript appearing in HTML script blocks.
     */
    static final JavaScriptEncoder JAVASCRIPT_BLOCK_ENCODER
            = map(JAVASCRIPT_BLOCK, new JavaScriptEncoder(JavaScriptEncoder.Mode.BLOCK, false));
    /**
     * Encoder for JavaScript in stand-alone contexts.
     */
    static final JavaScriptEncoder JAVASCRIPT_SOURCE_ENCODER
            = map(JAVASCRIPT_SOURCE, new JavaScriptEncoder(JavaScriptEncoder.Mode.SOURCE, false));
    /**
     * Encoder for full URIs.
     */
    static final URIEncoder URI_ENCODER
            = map(URI, new URIEncoder(URIEncoder.Mode.FULL_URI));
    /**
     * Encoder for components of URIs.
     */
    static final URIEncoder URI_COMPONENT_ENCODER
            = map(URI_COMPONENT, new URIEncoder(URIEncoder.Mode.COMPONENT));
    /**
     * Encoder for Java strings.
     */
    static final JavaEncoder JAVA_ENCODER
            = map(JAVA, new JavaEncoder());
    /**
     * Encoder for CSS strings.
     */
    static final CSSEncoder CSS_STRING_ENCODER
            = map(CSS_STRING, new CSSEncoder(CSSEncoder.Mode.STRING));
    /**
     * Encoder for CSS URL values.
     */
    static final CSSEncoder CSS_URL_ENCODER
            = map(CSS_URL, new CSSEncoder(CSSEncoder.Mode.URL));

    /**
     * Internal method to setup and map encoder singletons.
     *
     * @param name -- name of the encoder (one of the constants above)
     * @param encoder -- the encoder singleton instance
     * @param <T> the encoder type
     * @return the encoder argument.
     */
    private static <T extends Encoder> T map(String name, T encoder) {
        Encoder old = ENCODERS_MAP.put(name, encoder);
        assert old == null;
        return encoder;
    }

    /**
     * Returns a new instance of an Encoder for the specified context. The
     * returned instance is thread-safe.
     *
     * @param contextName the context name (one of the String constants defined
     * in this class)
     * @return an encoder for the specified context.
     * @throws NullPointerException if {@code contextName} is null
     * @throws UnsupportedContextException if {@code contextName} is not
     * recognized.
     */
    public static Encoder forName(String contextName) throws NullPointerException, UnsupportedContextException {
        if (contextName == null) {
            throw new NullPointerException();
        }
        Encoder encoder = ENCODERS_MAP.get(contextName);
        if (encoder == null) {
            throw new UnsupportedContextException(contextName);
        }
        return encoder;
    }

    /**
     * No instances.
     */
    private Encoders() {
    }
}