    001/*
    002    Licensed to the Apache Software Foundation (ASF) under one
    003    or more contributor license agreements.  See the NOTICE file
    004    distributed with this work for additional information
    005    regarding copyright ownership.  The ASF licenses this file
    006    to you under the Apache License, Version 2.0 (the
    007    "License"); you may not use this file except in compliance
    008    with the License.  You may obtain a copy of the License at
    009
    010       http://www.apache.org/licenses/LICENSE-2.0
    011
    012    Unless required by applicable law or agreed to in writing,
    013    software distributed under the License is distributed on an
    014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015    KIND, either express or implied.  See the License for the
    016    specific language governing permissions and limitations
    017    under the License.
    018 */
    019package org.apache.wiki.ui;
    020
    021import org.apache.wiki.api.core.Context;
    022import org.apache.wiki.api.core.Session;
    023import org.apache.wiki.i18n.InternationalizationManager;
    024import org.apache.wiki.preferences.Preferences;
    025
    026import java.text.MessageFormat;
    027import java.util.ResourceBundle;
    028import java.util.regex.Matcher;
    029import java.util.regex.Pattern;
    030
    031/**
    032 * Provides basic validation services for HTTP parameters. Three standard validators are provided: email address, identifier and
    033 * standard input. Standard input validator will reject any HTML-like input, and any of a number of special characters. ID validator
    034 * rejects HTML and quoted strings, and a couple of special characters.
    035 *
    036 * @since 2.3.54
    037 */
    038public final class InputValidator {
    039
    040    /** Standard input validator. */
    041    public static final int STANDARD = 0;
    042    /** Input validator for e-mail addresses. **/
    043    public static final int EMAIL    = 1;
    044    /** @since 2.4.82 */
    045    public static final int ID        = 2;
    046
    047    /**
    048     * Used when checking against IDs such as a full name when saving groups.
    049     * @since 2.4.82
    050     */
    051    static final Pattern ID_PATTERN     = Pattern.compile( "[\\x00\\r\\n\\x0f\"'<>;&\\xff{}]" );
    052    static final Pattern EMAIL_PATTERN  = Pattern.compile( "^[0-9a-zA-Z-_.+]+@([0-9a-zA-Z-_]+\\.)+[a-zA-Z]+$" );
    053    static final Pattern UNSAFE_PATTERN = Pattern.compile( "[\\x00\\r\\n\\x0f\"':<>\\[\\];#&@\\xff{}$%\\\\]" );
    054
    055    private final String m_form;
    056    private final Session m_session;
    057    private final Context m_context;
    058
    059    /**
    060     * Constructs a new input validator for a specific form and wiki session. When validation errors are detected, they will be added to
    061     * the wiki session's messages.
    062     *
    063     * @param form the ID or name of the form this validator should be associated with
    064     * @param context the wiki context
    065     */
    066    public InputValidator( final String form, final Context context ) {
    067        m_form = form;
    068        m_context = context;
    069        m_session = context.getWikiSession();
    070    }
    071
    072    /**
    073     * Validates a string against the {@link #STANDARD} validator and additionally checks that the value is not <code>null</code> or blank.
    074     *
    075     * @param input the string to validate
    076     * @param label the label for the string or field ("E-mail address")
    077     * @return returns <code>true</code> if valid, <code>false</code> otherwise
    078     */
    079    public boolean validateNotNull( final String input, final String label ) {
    080        return validateNotNull( input, label, STANDARD );
    081    }
    082
    083    /**
    084     * Validates a string against a particular pattern type and additionally checks that the value is not <code>null</code> or blank.
    085     * Delegates to {@link #validate(String, String, int)}.
    086     *
    087     * @param input the string to validate
    088     * @param label the label for the string or field ("E-mail address")
    089     * @param type the pattern type to use (<em>e.g.</em>, {@link #STANDARD}, {@link #EMAIL}.
    090     * @return returns <code>true</code> if valid, <code>false</code> otherwise
    091     */
    092    public boolean validateNotNull( final String input, final String label, final int type ) {
    093        if ( isBlank( input ) ) {
    094            final ResourceBundle rb = Preferences.getBundle( m_context, InternationalizationManager.CORE_BUNDLE );
    095            m_session.addMessage( m_form, MessageFormat.format( rb.getString("validate.cantbenull"), label ) );
    096            return false;
    097        }
    098        return validate( input, label, type ) && !isBlank( input );
    099    }
    100
    101    /**
    102     * Validates a string against a particular pattern type: e-mail address, standard HTML input, etc. Note that a blank or null string will
    103     * always validate.
    104     *
    105     * @param input the string to validate
    106     * @param label the label for the string or field ("E-mail address")
    107     * @param type the target pattern to validate against ({@link #STANDARD}, {@link #EMAIL})
    108     * @return returns <code>true</code> if valid, <code>false</code> otherwise
    109     */
    110    public boolean validate( final String input, final String label, final int type ) {
    111        // If blank, it's valid
    112        if ( isBlank( input ) ) {
    113            return true;
    114        }
    115
    116        final ResourceBundle rb = Preferences.getBundle( m_context, InternationalizationManager.CORE_BUNDLE );
    117
    118        // Otherwise, see if it matches the pattern for the target type
    119        final Matcher matcher;
    120        final boolean valid;
    121        switch( type ) {
    122        case STANDARD:
    123            matcher = UNSAFE_PATTERN.matcher( input );
    124            valid = !matcher.find();
    125            if ( !valid ) {
    126                // MessageTag already invokes replaceEntities()
    127                // Object[] args = { label, "&quot;&#39;&lt;&gt;;&amp;[]#\\@{}%$" };
    128                final Object[] args = { label, "'\"<>;&[]#\\@{}%$" };
    129                m_session.addMessage( m_form, MessageFormat.format( rb.getString( "validate.unsafechars" ), args ) );
    130            }
    131            return valid;
    132        case EMAIL:
    133            matcher = EMAIL_PATTERN.matcher( input );
    134            valid = matcher.matches();
    135            if ( !valid ) {
    136                final Object[] args = { label };
    137                m_session.addMessage( m_form, MessageFormat.format( rb.getString( "validate.invalidemail" ), args ) );
    138            }
    139            return valid;
    140        case ID:
    141            matcher = ID_PATTERN.matcher( input );
    142            valid = !matcher.find();
    143            if ( !valid ) {
    144                // MessageTag already invokes replaceEntities()
    145                // Object[] args = { label, "&quot;&#39;&lt;&gt;;&amp;{}" };
    146                final Object[] args = { label, "'\"<>;&{}" };
    147                m_session.addMessage( m_form, MessageFormat.format( rb.getString( "validate.unsafechars" ), args ) );
    148            }
    149            return valid;
    150         default:
    151             break;
    152        }
    153        throw new IllegalArgumentException( "Invalid input type." );
    154    }
    155
    156    /**
    157     * Returns <code>true</code> if a supplied string is null or blank
    158     *
    159     * @param input the string to check
    160     * @return <code>true</code> if <code>null</code> or blank (zero-length); <code>false</code> otherwise
    161     */
    162    public static boolean isBlank( final String input ) {
    163        return input == null || input.trim().isEmpty();
    164    }
    165
    166}