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, ""'<>;&[]#\\@{}%$" }; 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, ""'<>;&{}" }; 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().length() < 1; 164 } 165 166}