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     */
019    package org.apache.wiki.ui;
020    
021    import java.text.MessageFormat;
022    import java.util.ResourceBundle;
023    import java.util.regex.Matcher;
024    import java.util.regex.Pattern;
025    
026    import org.apache.wiki.*;
027    import org.apache.wiki.i18n.InternationalizationManager;
028    import 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     */
037    public 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                    Object[] args = { label, "&quot;&#39;&lt;&gt;;&amp;[]#\\@{}%$" };
147                    m_session.addMessage( m_form, MessageFormat.format( rb.getString("validate.unsafechars"),
148                                                                        args ) );
149                }
150                return valid;
151            case EMAIL:
152                matcher = EMAIL_PATTERN.matcher( input );
153                valid = matcher.matches();
154                if ( !valid )
155                {
156                    Object[] args = { label };
157                    m_session.addMessage( m_form, MessageFormat.format( rb.getString("validate.invalidemail"),
158                                                                        args ) );
159                }
160                return valid;
161            case ID:
162                matcher = ID_PATTERN.matcher( input );
163                valid = !matcher.find();
164                if ( !valid )
165                {
166                    Object[] args = { label, "&quot;&#39;&lt;&gt;;&amp;{}" };
167                    m_session.addMessage( m_form, MessageFormat.format( rb.getString("validate.unsafechars"),
168                                                                        args ) );
169                }
170                return valid;
171             default:
172                 break;
173            }
174            throw new IllegalArgumentException( "Invalid input type." );
175        }
176    
177        /**
178         * Returns <code>true</code> if a supplied string is null or blank
179         * @param input the string to check
180         * @return <code>true</code> if <code>null</code> or blank (zero-length);
181         * <code>false</code> otherwise
182         */
183        public static boolean isBlank( String input )
184        {
185            return input == null || input.trim().length() < 1;
186        }
187    }