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, ""'<>;&[]#\\@{}%$" }; 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, ""'<>;&{}" }; 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}