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 java.text.MessageFormat;
022import java.util.ResourceBundle;
023import java.util.regex.Matcher;
024import java.util.regex.Pattern;
025
026import org.apache.wiki.*;
027import org.apache.wiki.i18n.InternationalizationManager;
028import org.apache.wiki.preferences.Preferences;
029
030/**
031 * Provides basic validation services for HTTP parameters. Three standard
032 * validators are provided: email address, identifier and standard input. Standard input
033 * validator will reject any HTML-like input, and any of a number of special
034 * characters.  ID validator rejects HTML and quoted strings, and a couple of special characters.
035 * @since 2.3.54
036 */
037public final class InputValidator
038{
039    /** Standard input validator. */
040    public static final int        STANDARD       = 0;
041
042    /** Input validator for e-mail addresses. **/
043    public static final int        EMAIL          = 1;
044
045    /**
046     * @since 2.4.82
047     */
048    public static final int        ID             = 2;
049
050    protected static final Pattern EMAIL_PATTERN  = Pattern.compile( "^[0-9a-zA-Z-_\\.\\+]+@([0-9a-zA-Z-_]+\\.)+[a-zA-Z]+$" );
051
052    protected static final Pattern UNSAFE_PATTERN = Pattern.compile( "[\\x00\\r\\n\\x0f\"':<>\\[\\];#&@\\xff{}\\$%\\\\]" );
053
054    /** Used when checking against IDs such as a full name when saving groups.
055     *  @since 2.4.82 */
056    protected static final Pattern ID_PATTERN     = Pattern.compile( "[\\x00\\r\\n\\x0f\"'<>;&\\xff{}]" );
057
058    private final String           m_form;
059
060    private final WikiSession      m_session;
061
062    private final WikiContext      m_context;
063
064    /**
065     * Constructs a new input validator for a specific form and wiki session.
066     * When validation errors are detected, they will be added to the wiki
067     * session's messages.
068     * @param form the ID or name of the form this validator should be
069     * associated with
070     * @param context the wiki context
071     */
072    public InputValidator( String form, WikiContext context )
073    {
074        m_form = form;
075        m_context = context;
076        m_session = context.getWikiSession();
077    }
078
079    /**
080     * Validates a string against the {@link #STANDARD} validator and
081     * additionally checks that the value is not <code>null</code> or blank.
082     * @param input the string to validate
083     * @param label the label for the string or field ("E-mail address")
084     * @return returns <code>true</code> if valid, <code>false</code>
085     * otherwise
086     */
087    public boolean validateNotNull( String input, String label )
088    {
089        return validateNotNull( input, label, STANDARD );
090    }
091
092    /**
093     * Validates a string against a particular pattern type and additionally
094     * checks that the value is not <code>null</code> or blank. Delegates to
095     * {@link #validate(String, String, int)}.
096     * @param input the string to validate
097     * @param label the label for the string or field ("E-mail address")
098     * @param type the pattern type to use (<em>e.g.</em>, {@link #STANDARD}, {@link #EMAIL}.
099     * @return returns <code>true</code> if valid, <code>false</code>
100     * otherwise
101     */
102    public boolean validateNotNull( String input, String label, int type )
103    {
104        if ( isBlank( input ) )
105        {
106            ResourceBundle rb = Preferences.getBundle( m_context, InternationalizationManager.CORE_BUNDLE );
107
108            m_session.addMessage( m_form, MessageFormat.format( rb.getString("validate.cantbenull"),
109                                                                label ) );
110            return false;
111        }
112        return validate( input, label, type ) && !isBlank( input );
113    }
114
115    /**
116     * Validates a string against a particular pattern type: e-mail address,
117     * standard HTML input, etc. Note that a blank or null string will
118     * always validate.
119     * @param input the string to validate
120     * @param label the label for the string or field ("E-mail address")
121     * @param type the target pattern to validate against ({@link #STANDARD},
122     * {@link #EMAIL})
123     * @return returns <code>true</code> if valid, <code>false</code>
124     * otherwise
125     */
126    public boolean validate( String input, String label, int type )
127    {
128        // If blank, it's valid
129        if ( isBlank( input ) )
130        {
131            return true;
132        }
133
134        ResourceBundle rb = Preferences.getBundle( m_context, InternationalizationManager.CORE_BUNDLE );
135
136        // Otherwise, see if it matches the pattern for the target type
137        Matcher matcher;
138        boolean valid;
139        switch( type )
140        {
141        case STANDARD:
142            matcher = UNSAFE_PATTERN.matcher( input );
143            valid = !matcher.find();
144            if ( !valid )
145            {
146                //MessageTag already invokes replaceEntities()
147                //Object[] args = { label, "&quot;&#39;&lt;&gt;;&amp;[]#\\@{}%$" };
148                Object[] args = { label, "\'\"<>;&[]#\\@{}%$" };
149                m_session.addMessage( m_form, MessageFormat.format( rb.getString("validate.unsafechars"), args ) );
150            }
151            return valid;
152        case EMAIL:
153            matcher = EMAIL_PATTERN.matcher( input );
154            valid = matcher.matches();
155            if ( !valid )
156            {
157                Object[] args = { label };
158                m_session.addMessage( m_form, MessageFormat.format( rb.getString("validate.invalidemail"), args ) );
159            }
160            return valid;
161        case ID:
162            matcher = ID_PATTERN.matcher( input );
163            valid = !matcher.find();
164            if ( !valid )
165            {
166                //MessageTag already invokes replaceEntities()
167                //Object[] args = { label, "&quot;&#39;&lt;&gt;;&amp;{}" };
168                Object[] args = { label, "\'\"<>;&{}" };
169                m_session.addMessage( m_form, MessageFormat.format( rb.getString("validate.unsafechars"), args ) );
170            }
171            return valid;
172         default:
173             break;
174        }
175        throw new IllegalArgumentException( "Invalid input type." );
176    }
177
178    /**
179     * Returns <code>true</code> if a supplied string is null or blank
180     * @param input the string to check
181     * @return <code>true</code> if <code>null</code> or blank (zero-length);
182     * <code>false</code> otherwise
183     */
184    public static boolean isBlank( String input )
185    {
186        return input == null || input.trim().length() < 1;
187    }
188}