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}