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;
030import org.apache.commons.validator.routines.EmailValidator;
031
032/**
033 * Provides basic validation services for HTTP parameters. Three standard validators are provided: email address, identifier and
034 * standard input. Standard input validator will reject any HTML-like input, and any of a number of special characters. ID validator
035 * rejects HTML and quoted strings, and a couple of special characters.
036 *
037 * @since 2.3.54
038 */
039public final class InputValidator {
040
041    /** Standard input validator. */
042    public static final int STANDARD = 0;
043    /** Input validator for e-mail addresses. **/
044    public static final int EMAIL    = 1;
045    /** @since 2.4.82 */
046    public static final int ID        = 2;
047
048    /**
049     * Used when checking against IDs such as a full name when saving groups.
050     * @since 2.4.82
051     */
052    static final Pattern ID_PATTERN     = Pattern.compile( "[\\x00\\r\\n\\x0f\"'<>;&\\xff{}]" );
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            valid = EmailValidator.getInstance().isValid(input);
134            if ( !valid ) {
135                final Object[] args = { label };
136                m_session.addMessage( m_form, MessageFormat.format( rb.getString( "validate.invalidemail" ), args ) );
137            }
138            return valid;
139        case ID:
140            matcher = ID_PATTERN.matcher( input );
141            valid = !matcher.find();
142            if ( !valid ) {
143                // MessageTag already invokes replaceEntities()
144                // Object[] args = { label, "&quot;&#39;&lt;&gt;;&amp;{}" };
145                final Object[] args = { label, "'\"<>;&{}" };
146                m_session.addMessage( m_form, MessageFormat.format( rb.getString( "validate.unsafechars" ), args ) );
147            }
148            return valid;
149         default:
150             break;
151        }
152        throw new IllegalArgumentException( "Invalid input type." );
153    }
154
155    /**
156     * Returns <code>true</code> if a supplied string is null or blank
157     *
158     * @param input the string to check
159     * @return <code>true</code> if <code>null</code> or blank (zero-length); <code>false</code> otherwise
160     */
161    public static boolean isBlank( final String input ) {
162        return input == null || input.trim().isEmpty();
163    }
164
165}