001/* 002 * Copyright © 2025 CUI-OpenSource-Software (info@cuioss.de) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package de.cuioss.http.security.validation; 017 018import de.cuioss.http.security.config.SecurityConfiguration; 019import de.cuioss.http.security.core.HttpSecurityValidator; 020import de.cuioss.http.security.core.UrlSecurityFailureType; 021import de.cuioss.http.security.core.ValidationType; 022import de.cuioss.http.security.exceptions.UrlSecurityException; 023import org.jspecify.annotations.Nullable; 024 025import java.util.Optional; 026import java.util.function.Predicate; 027 028/** 029 * Length validation stage with configurable limits for HTTP components. 030 * 031 * <p>This stage enforces length limits on various HTTP components to prevent 032 * denial-of-service attacks, buffer overflow attempts, and resource exhaustion. 033 * The stage validates input length against component-specific limits:</p> 034 * 035 * <ol> 036 * <li><strong>Path Length Validation</strong> - Enforces maximum URL path length</li> 037 * <li><strong>Parameter Length Validation</strong> - Validates parameter names and values</li> 038 * <li><strong>Header Length Validation</strong> - Checks header names and values</li> 039 * <li><strong>Cookie Length Validation</strong> - Validates cookie names and values</li> 040 * <li><strong>Body Size Validation</strong> - Enforces request/response body size limits</li> 041 * </ol> 042 * 043 * <h3>Design Principles</h3> 044 * <ul> 045 * <li><strong>DoS Protection</strong> - Prevents resource exhaustion through size limits</li> 046 * <li><strong>Context-Sensitive</strong> - Different limits for different HTTP components</li> 047 * <li><strong>Performance Optimized</strong> - Simple length checks with O(1) complexity</li> 048 * <li><strong>RFC Compliant</strong> - Follows HTTP specification recommendations</li> 049 * </ul> 050 * 051 * <h3>Security Validations</h3> 052 * <ul> 053 * <li><strong>Path Length</strong> - Prevents extremely long URL paths</li> 054 * <li><strong>Parameter Length</strong> - Limits parameter name and value sizes</li> 055 * <li><strong>Header Length</strong> - Enforces HTTP header size restrictions</li> 056 * <li><strong>Cookie Length</strong> - Validates cookie name and value sizes</li> 057 * <li><strong>Body Size</strong> - Prevents large payload attacks</li> 058 * </ul> 059 * 060 * <h3>Usage Examples</h3> 061 * <pre> 062 * // Create length validation stage 063 * SecurityConfiguration config = SecurityConfiguration.defaults(); 064 * LengthValidationStage lengthValidator = new LengthValidationStage(config, ValidationType.URL_PATH); 065 * 066 * // Validate path length 067 * try { 068 * lengthValidator.validate("/api/users/123"); // Passes if within limit 069 * } catch (UrlSecurityException e) { 070 * logger.warn("Path too long: {}", e.getFailureType()); 071 * } 072 * 073 * // Validate parameter value 074 * LengthValidationStage paramValidator = new LengthValidationStage(config, ValidationType.PARAMETER_VALUE); 075 * try { 076 * paramValidator.validate("very_long_parameter_value"); // May fail if too long 077 * } catch (UrlSecurityException e) { 078 * logger.warn("Parameter value too long: {}", e.getDetail()); 079 * } 080 * 081 * // Validate with custom limits 082 * SecurityConfiguration strictConfig = SecurityConfiguration.builder() 083 * .maxPathLength(1024) 084 * .maxParameterValueLength(256) 085 * .build(); 086 * LengthValidationStage strictValidator = new LengthValidationStage(strictConfig, ValidationType.URL_PATH); 087 * </pre> 088 * 089 * <h3>Performance Characteristics</h3> 090 * <ul> 091 * <li>O(1) time complexity - simple length comparison</li> 092 * <li>Minimal memory overhead - no string manipulation</li> 093 * <li>Early termination on limit exceeded</li> 094 * <li>No regex or pattern matching overhead</li> 095 * </ul> 096 * 097 * <h3>Configuration Dependencies</h3> 098 * <ul> 099 * <li><strong>maxPathLength</strong> - Maximum allowed path length</li> 100 * <li><strong>maxParameterNameLength/maxParameterValueLength</strong> - Parameter size limits</li> 101 * <li><strong>maxHeaderNameLength/maxHeaderValueLength</strong> - Header size limits</li> 102 * <li><strong>maxCookieNameLength/maxCookieValueLength</strong> - Cookie size limits</li> 103 * <li><strong>maxBodySize</strong> - Maximum body size in bytes</li> 104 * </ul> 105 * <p> 106 * Implements: Task V4 from HTTP verification specification 107 * 108 * @see HttpSecurityValidator 109 * @see SecurityConfiguration 110 * @see ValidationType 111 * @since 1.0 112 */ 113public record LengthValidationStage( 114SecurityConfiguration config, 115ValidationType validationType) implements HttpSecurityValidator { 116 117 /** 118 * Validates input length against component-specific limits. 119 * 120 * <p>Processing logic:</p> 121 * <ol> 122 * <li>Input validation - handles null/empty inputs</li> 123 * <li>Length calculation - gets input length in characters or bytes</li> 124 * <li>Limit lookup - determines appropriate limit based on validation type</li> 125 * <li>Comparison - checks if input exceeds configured limit</li> 126 * </ol> 127 * 128 * @param value The input string to validate for length limits 129 * @return The original input wrapped in Optional if validation passes, Optional.empty() if input was null 130 * @throws UrlSecurityException if length limits are exceeded: 131 * <ul> 132 * <li>PATH_TOO_LONG - if URL path exceeds maximum length</li> 133 * <li>INPUT_TOO_LONG - if other components exceed their limits</li> 134 * </ul> 135 */ 136 @Override 137 public Optional<String> validate(@Nullable String value) throws UrlSecurityException { 138 if (value == null) { 139 return Optional.empty(); 140 } 141 142 // Get input length in characters 143 int inputLength = value.length(); 144 145 // Determine the appropriate limit and failure type based on validation type 146 int limit = getMaxLength(); 147 UrlSecurityFailureType failureType = getFailureType(); 148 String componentName = getComponentName(); 149 150 // Check if input exceeds the limit 151 if (inputLength > limit) { 152 throw UrlSecurityException.builder() 153 .failureType(failureType) 154 .validationType(validationType) 155 .originalInput(value) 156 .detail(componentName + " length " + inputLength + " exceeds maximum " + limit) 157 .build(); 158 } 159 160 // Validation passed - return original value 161 return Optional.of(value); 162 } 163 164 /** 165 * Gets the maximum allowed length for the current validation type. 166 * 167 * @return Maximum length in characters (or bytes for body content) 168 */ 169 private int getMaxLength() { 170 return switch (validationType) { 171 case URL_PATH -> config.maxPathLength(); 172 case PARAMETER_NAME -> config.maxParameterNameLength(); 173 case PARAMETER_VALUE -> config.maxParameterValueLength(); 174 case HEADER_NAME -> config.maxHeaderNameLength(); 175 case HEADER_VALUE -> config.maxHeaderValueLength(); 176 case COOKIE_NAME -> config.maxCookieNameLength(); 177 case COOKIE_VALUE -> config.maxCookieValueLength(); 178 case BODY -> (int) Math.min(config.maxBodySize(), Integer.MAX_VALUE); 179 }; 180 } 181 182 /** 183 * Gets the appropriate failure type for the current validation type. 184 * 185 * @return UrlSecurityFailureType corresponding to the validation context 186 */ 187 private UrlSecurityFailureType getFailureType() { 188 return switch (validationType) { 189 case URL_PATH -> UrlSecurityFailureType.PATH_TOO_LONG; 190 case PARAMETER_NAME, PARAMETER_VALUE, HEADER_NAME, HEADER_VALUE, 191 COOKIE_NAME, COOKIE_VALUE, BODY -> UrlSecurityFailureType.INPUT_TOO_LONG; 192 }; 193 } 194 195 /** 196 * Gets a human-readable component name for error messages. 197 * 198 * @return Component name string for use in error details 199 */ 200 private String getComponentName() { 201 return switch (validationType) { 202 case URL_PATH -> "URL path"; 203 case PARAMETER_NAME -> "Parameter name"; 204 case PARAMETER_VALUE -> "Parameter value"; 205 case HEADER_NAME -> "Header name"; 206 case HEADER_VALUE -> "Header value"; 207 case COOKIE_NAME -> "Cookie name"; 208 case COOKIE_VALUE -> "Cookie value"; 209 case BODY -> "Request body"; 210 }; 211 } 212 213 /** 214 * Creates a conditional validator that only processes inputs matching the condition. 215 * 216 * @param condition The condition to test before validation 217 * @return A conditional HttpSecurityValidator that applies length validation conditionally 218 */ 219 @Override 220 public HttpSecurityValidator when(Predicate<String> condition) { 221 return input -> { 222 if (input != null && condition.test(input)) { 223 return validate(input); 224 } 225 return Optional.ofNullable(input); 226 }; 227 } 228 229 230}