src/main/java/org/apache/sling/auth/core/spi/AbstractAuthenticationFormServlet.java [49:340]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    public static final String DEFAULT_FORM_PATH = "login.html";

    /**
     * The path to the custom login form.
     *
     * @see #getCustomFormPath()
     */
    public static final String CUSTOM_FORM_PATH = "custom_login.html";

    /**
     * The raw form used by the {@link #getForm(HttpServletRequest)} method to
     * fill in with per-request data. This field is set by the
     * {@link #getRawForm()} method when first loading the form.
     */
    private final AtomicReference<String> rawForm = new AtomicReference<>();

    /**
     * Prepares and returns the login form. The response is sent as an UTF-8
     * encoded <code>text/html</code> page with all known cache control headers
     * set to prevent all caching.
     * <p>
     * This servlet is to be called to handle the request directly, that is it
     * expected to not be included and for the response to not be committed yet
     * because it first resets the response.
     *
     * @throws IOException if an error occurs preparing or sending back the
     *             login form
     * @throws IllegalStateException if the response has already been committed
     *             and thus response reset is not possible.
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        handle(request, response);
    }

    /**
     * Prepares and returns the login form. The response is sent as an UTF-8
     * encoded <code>text/html</code> page with all known cache control headers
     * set to prevent all caching.
     * <p>
     * This servlet is to be called to handle the request directly, that is it
     * expected to not be included and for the response to not be committed yet
     * because it first resets the response.
     *
     * @throws IOException if an error occurs preparing or sending back the
     *             login form
     * @throws IllegalStateException if the response has already been committed
     *             and thus response reset is not possible.
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        handle(request, response);
    }

    private void handle(HttpServletRequest request, HttpServletResponse response) {
        try {
            // reset the response first
            response.reset();

            // setup the response for HTML and cache prevention
            response.setContentType("text/html");
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Cache-Control", "no-cache");
            response.addHeader("Cache-Control", "no-store");
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Expires", "0");

            // send the form and flush
            response.getWriter().print(getForm(request));
            response.flushBuffer();
        } catch (IOException ioe) {
            log("Unexpected exception caught", ioe);
            try {
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            } catch (IOException ioe2) {
                log("Unexpected exception caught while sending the error", ioe2);
            }
        }
    }

    /**
     * Returns the form to be sent back to the client for login providing an
     * optional informational message and the optional target to redirect to
     * after successfully logging in.
     *
     * @param request The request providing parameters indicating the
     *            informational message and redirection target.
     * @return The login form to be returned to the client
     * @throws IOException If the login form cannot be loaded
     */
    protected String getForm(final HttpServletRequest request) throws IOException {
        String form = getRawForm();

        final String resource = cleanse(request, getResource(request));
        final String reason = getReason(request);
        final String resourceContextPath = cleanse(request, getContextPath(request));
        final String contextPath = request.getContextPath();

        // replace form placeholders with checked and filtered values
        form = form.replace("${resource}", escape(resource));
        form = form.replace("${j_reason}", escape(reason));
        form = form.replace("${requestContextPath}", escape(resourceContextPath));
        form = form.replace("${contextPath}", escape(contextPath));

        return form;
    }

    /**
     * Makes sure the given {@code target} is not pointing to some absolute
     * location outside of the given {@code request} context. If so, the target
     * must be ignored and an empty string is returned.
     * <p>
     * This method uses the
     * {@link AuthUtil#isRedirectValid(HttpServletRequest, String)} method.
     *
     * @param request The {@code HttpServletRequest} to test the {@code target}
     *            against.
     * @param target The target location (URL) to test for validity.
     * @return The target location if not pointing outside of the current
     *         request or an empty string.
     */
    private static String cleanse(final HttpServletRequest request, final String target) {
        if (target.length() > 0 && !AuthUtil.isRedirectValid(request, target)) {
            return "";
        }
        return target;
    }

    /**
     * Escape the output.
     * This method does a simple XML escaping for '<', '>' and '&'
     * and also escapes single and double quotes.
     * As these characters should never occur in a url this encoding should
     * be fine.
     * @param input The string to escape
     * @return The escaped string or {@code null} if the input is {@code null}
     */
    private static String escape(final String input) {
        if (input == null) {
            return null;
        }

        final StringBuilder b = new StringBuilder(input.length());
        for (int i = 0; i < input.length(); i++) {
            final char c = input.charAt(i);
            if (c == '&') {
                b.append("&amp;");
            } else if (c == '<') {
                b.append("&lt;");
            } else if (c == '>') {
                b.append("&gt;");
            } else if (c == '"') {
                b.append("%22");
            } else if (c == '\'') {
                b.append("%27");
            } else {
                b.append(c);
            }
        }
        return b.toString();
    }

    /**
     * Returns the path to the resource to which the request should be
     * redirected after successfully completing the form or an empty string if
     * there is no <code>resource</code> request parameter.
     *
     * @param request The request providing the <code>resource</code> parameter.
     * @return The target to redirect after successfully login or an empty string
     *         if no specific target has been requested.
     */
    protected String getResource(final HttpServletRequest request) {
        return AuthUtil.getLoginResource(request, "");
    }

    /**
     * Returns an informational message according to the value provided in the
     * <code>j_reason</code> request parameter. Supported reasons are invalid
     * credentials and session timeout.
     *
     * @param request The request providing the parameter
     * @return The "translated" reason to render the login form or an empty
     *         string if there is no specific reason
     */
    protected abstract String getReason(final HttpServletRequest request);

    /**
     * Returns the context path for the authentication form request. This path
     * is the path to the authenticated resource as returned by
     * {@link #getResource(HttpServletRequest)} (without the optional query
     * string which may be contained in the resource path). If {@link #getResource(HttpServletRequest)}
     * return an empty string, the servlet context path is used.
     *
     * @param request The request
     * @return The context path for the form action consisting of the resource to
     *         which the user is to authenticate.
     */
    protected String getContextPath(final HttpServletRequest request) {
        String contextPath = getResource(request);
        if ("".equals(contextPath)) {
            contextPath = request.getContextPath();
        }
        int query = contextPath.indexOf('?');
        if (query > 0) {
            contextPath = contextPath.substring(0, query);
        }

        return removeEndingSlash(contextPath);
    }

    private static String removeEndingSlash(String str) {
        if (str != null && str.endsWith("/")) {
            return str.substring(0, str.length() - 1);
        }
        return str;
    }

    /**
     * Load the raw unmodified form from the bundle (through the class loader).
     *
     * @return The raw form as a string
     * @throws IOException If an error occurs reading the "file" or if the
     *             class loader cannot provide the form data.
     */
    private String getRawForm() throws IOException {
        String value = rawForm.get();
        if (value == null) {
            try (InputStream ins = getLoginFormStream();
                    Reader r = ins == null ? null : new InputStreamReader(ins, StandardCharsets.UTF_8)) {
                if (r != null) {
                    StringBuilder builder = new StringBuilder();

                    char[] cbuf = new char[1024];
                    int rd = 0;
                    while ((rd = r.read(cbuf)) >= 0) {
                        builder.append(cbuf, 0, rd);
                    }

                    value = builder.toString();
                    rawForm.set(value);
                }
            }

            if (value == null) {
                throw new IOException("Failed reading form template");
            }
        }
        return value;
    }

    /**
     * Get login form resource as an input stream
     * @return login form input stream or null if not found
     */
    private InputStream getLoginFormStream() {
        // try a custom login page first.
        InputStream ins = getClass().getResourceAsStream(getCustomFormPath());
        if (ins == null) {
            // try the standard login page
            ins = getClass().getResourceAsStream(getDefaultFormPath());
        }
        return ins;
    }

    /**
     * Returns the path to the default login form to load through the class
     * loader of this instance using <code>Class.getResourceAsStream</code>.
     * <p>
     * The default form is used intended to be included with the bundle
     * implementing this abstract class.
     * <p>
     * This method returns {@link #DEFAULT_FORM_PATH} and may be overwritten by
     * implementations.
     * @return {@link #DEFAULT_FORM_PATH}
     */
    protected String getDefaultFormPath() {
        return DEFAULT_FORM_PATH;
    }

    /**
     * Returns the path to the custom login form to load through the class
     * loader of this instance using <code>Class.getResourceAsStream</code>.
     * <p>
     * The custom form can be supplied by a fragment attaching to the bundle
     * implementing this abstract class.
     * <p>
     * This method returns {@link #CUSTOM_FORM_PATH} and may be overwritten by
     * implementations.
     * @return  {@link #CUSTOM_FORM_PATH}
     */
    protected String getCustomFormPath() {
        return CUSTOM_FORM_PATH;
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



src/main/java/org/apache/sling/auth/core/spi/AbstractJakartaAuthenticationFormServlet.java [47:338]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    public static final String DEFAULT_FORM_PATH = "login.html";

    /**
     * The path to the custom login form.
     *
     * @see #getCustomFormPath()
     */
    public static final String CUSTOM_FORM_PATH = "custom_login.html";

    /**
     * The raw form used by the {@link #getForm(HttpServletRequest)} method to
     * fill in with per-request data. This field is set by the
     * {@link #getRawForm()} method when first loading the form.
     */
    private final AtomicReference<String> rawForm = new AtomicReference<>();

    /**
     * Prepares and returns the login form. The response is sent as an UTF-8
     * encoded <code>text/html</code> page with all known cache control headers
     * set to prevent all caching.
     * <p>
     * This servlet is to be called to handle the request directly, that is it
     * expected to not be included and for the response to not be committed yet
     * because it first resets the response.
     *
     * @throws IOException if an error occurs preparing or sending back the
     *             login form
     * @throws IllegalStateException if the response has already been committed
     *             and thus response reset is not possible.
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        handle(request, response);
    }

    /**
     * Prepares and returns the login form. The response is sent as an UTF-8
     * encoded <code>text/html</code> page with all known cache control headers
     * set to prevent all caching.
     * <p>
     * This servlet is to be called to handle the request directly, that is it
     * expected to not be included and for the response to not be committed yet
     * because it first resets the response.
     *
     * @throws IOException if an error occurs preparing or sending back the
     *             login form
     * @throws IllegalStateException if the response has already been committed
     *             and thus response reset is not possible.
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        handle(request, response);
    }

    private void handle(HttpServletRequest request, HttpServletResponse response) {
        try {
            // reset the response first
            response.reset();

            // setup the response for HTML and cache prevention
            response.setContentType("text/html");
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Cache-Control", "no-cache");
            response.addHeader("Cache-Control", "no-store");
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Expires", "0");

            // send the form and flush
            response.getWriter().print(getForm(request));
            response.flushBuffer();
        } catch (IOException ioe) {
            log("Unexpected exception caught", ioe);
            try {
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            } catch (IOException ioe2) {
                log("Unexpected exception caught while sending the error", ioe2);
            }
        }
    }

    /**
     * Returns the form to be sent back to the client for login providing an
     * optional informational message and the optional target to redirect to
     * after successfully logging in.
     *
     * @param request The request providing parameters indicating the
     *            informational message and redirection target.
     * @return The login form to be returned to the client
     * @throws IOException If the login form cannot be loaded
     */
    protected String getForm(final HttpServletRequest request) throws IOException {
        String form = getRawForm();

        final String resource = cleanse(request, getResource(request));
        final String reason = getReason(request);
        final String resourceContextPath = cleanse(request, getContextPath(request));
        final String contextPath = request.getContextPath();

        // replace form placeholders with checked and filtered values
        form = form.replace("${resource}", escape(resource));
        form = form.replace("${j_reason}", escape(reason));
        form = form.replace("${requestContextPath}", escape(resourceContextPath));
        form = form.replace("${contextPath}", escape(contextPath));

        return form;
    }

    /**
     * Makes sure the given {@code target} is not pointing to some absolute
     * location outside of the given {@code request} context. If so, the target
     * must be ignored and an empty string is returned.
     * <p>
     * This method uses the
     * {@link AuthUtil#isRedirectValid(HttpServletRequest, String)} method.
     *
     * @param request The {@code HttpServletRequest} to test the {@code target}
     *            against.
     * @param target The target location (URL) to test for validity.
     * @return The target location if not pointing outside of the current
     *         request or an empty string.
     */
    private static String cleanse(final HttpServletRequest request, final String target) {
        if (target.length() > 0 && !AuthUtil.isRedirectValid(request, target)) {
            return "";
        }
        return target;
    }

    /**
     * Escape the output.
     * This method does a simple XML escaping for '<', '>' and '&'
     * and also escapes single and double quotes.
     * As these characters should never occur in a url this encoding should
     * be fine.
     * @param input The string to escape
     * @return The escaped string or {@code null} if the input is {@code null}
     */
    private static String escape(final String input) {
        if (input == null) {
            return null;
        }

        final StringBuilder b = new StringBuilder(input.length());
        for (int i = 0; i < input.length(); i++) {
            final char c = input.charAt(i);
            if (c == '&') {
                b.append("&amp;");
            } else if (c == '<') {
                b.append("&lt;");
            } else if (c == '>') {
                b.append("&gt;");
            } else if (c == '"') {
                b.append("%22");
            } else if (c == '\'') {
                b.append("%27");
            } else {
                b.append(c);
            }
        }
        return b.toString();
    }

    /**
     * Returns the path to the resource to which the request should be
     * redirected after successfully completing the form or an empty string if
     * there is no <code>resource</code> request parameter.
     *
     * @param request The request providing the <code>resource</code> parameter.
     * @return The target to redirect after successfully login or an empty string
     *         if no specific target has been requested.
     */
    protected String getResource(final HttpServletRequest request) {
        return AuthUtil.getLoginResource(request, "");
    }

    /**
     * Returns an informational message according to the value provided in the
     * <code>j_reason</code> request parameter. Supported reasons are invalid
     * credentials and session timeout.
     *
     * @param request The request providing the parameter
     * @return The "translated" reason to render the login form or an empty
     *         string if there is no specific reason
     */
    protected abstract String getReason(final HttpServletRequest request);

    /**
     * Returns the context path for the authentication form request. This path
     * is the path to the authenticated resource as returned by
     * {@link #getResource(HttpServletRequest)} (without the optional query
     * string which may be contained in the resource path). If {@link #getResource(HttpServletRequest)}
     * return an empty string, the servlet context path is used.
     *
     * @param request The request
     * @return The context path for the form action consisting of the resource to
     *         which the user is to authenticate.
     */
    protected String getContextPath(final HttpServletRequest request) {
        String contextPath = getResource(request);
        if ("".equals(contextPath)) {
            contextPath = request.getContextPath();
        }
        int query = contextPath.indexOf('?');
        if (query > 0) {
            contextPath = contextPath.substring(0, query);
        }

        return removeEndingSlash(contextPath);
    }

    private static String removeEndingSlash(String str) {
        if (str != null && str.endsWith("/")) {
            return str.substring(0, str.length() - 1);
        }
        return str;
    }

    /**
     * Load the raw unmodified form from the bundle (through the class loader).
     *
     * @return The raw form as a string
     * @throws IOException If an error occurs reading the "file" or if the
     *             class loader cannot provide the form data.
     */
    private String getRawForm() throws IOException {
        String value = rawForm.get();
        if (value == null) {
            try (InputStream ins = getLoginFormStream();
                    Reader r = ins == null ? null : new InputStreamReader(ins, StandardCharsets.UTF_8)) {
                if (r != null) {
                    StringBuilder builder = new StringBuilder();

                    char[] cbuf = new char[1024];
                    int rd = 0;
                    while ((rd = r.read(cbuf)) >= 0) {
                        builder.append(cbuf, 0, rd);
                    }

                    value = builder.toString();
                    rawForm.set(value);
                }
            }

            if (value == null) {
                throw new IOException("Failed reading form template");
            }
        }
        return value;
    }

    /**
     * Get login form resource as an input stream
     * @return login form input stream or null if not found
     */
    private InputStream getLoginFormStream() {
        // try a custom login page first.
        InputStream ins = getClass().getResourceAsStream(getCustomFormPath());
        if (ins == null) {
            // try the standard login page
            ins = getClass().getResourceAsStream(getDefaultFormPath());
        }
        return ins;
    }

    /**
     * Returns the path to the default login form to load through the class
     * loader of this instance using <code>Class.getResourceAsStream</code>.
     * <p>
     * The default form is used intended to be included with the bundle
     * implementing this abstract class.
     * <p>
     * This method returns {@link #DEFAULT_FORM_PATH} and may be overwritten by
     * implementations.
     * @return {@link #DEFAULT_FORM_PATH}
     */
    protected String getDefaultFormPath() {
        return DEFAULT_FORM_PATH;
    }

    /**
     * Returns the path to the custom login form to load through the class
     * loader of this instance using <code>Class.getResourceAsStream</code>.
     * <p>
     * The custom form can be supplied by a fragment attaching to the bundle
     * implementing this abstract class.
     * <p>
     * This method returns {@link #CUSTOM_FORM_PATH} and may be overwritten by
     * implementations.
     * @return  {@link #CUSTOM_FORM_PATH}
     */
    protected String getCustomFormPath() {
        return CUSTOM_FORM_PATH;
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



