Source code for aidputils.agents.tools.http.template_engine

"""
Template Engine for HTTP Tool.

Provides secure template variable substitution with:
- {{variable}} syntax resolution
- URL encoding for template values
- Path traversal prevention
- Character validation (reject control chars, newlines)
- Length validation
"""

import re
import urllib.parse
from typing import Any, Dict, List, Optional

# Template pattern for {{variable}} syntax
TEMPLATE_PATTERN = re.compile(r"\{\{(\w+)\}\}")

# Maximum URL length
MAX_URL_LENGTH = 2048

# Dangerous patterns for path traversal
PATH_TRAVERSAL_PATTERNS = [
    "..",
    "//",
    "%2e%2e",  # URL-encoded ..
    "%2f%2f",  # URL-encoded //
    "%252e",   # Double-encoded .
    "%252f",   # Double-encoded /
]

# Control characters that should be rejected
CONTROL_CHAR_PATTERN = re.compile(r"[\x00-\x1f\x7f]")


[docs] class TemplateError(ValueError): """Exception raised for template-related errors.""" pass
[docs] class SecurityError(ValueError): """Exception raised for security-related validation failures.""" pass
[docs] def validate_template_value(value: str, context: str = "value") -> None: """ Validate a template value for security issues. Args: value: The value to validate context: Description of where this value is used (for error messages) Raises: SecurityError: If the value contains dangerous patterns """ # Check for control characters if CONTROL_CHAR_PATTERN.search(value): raise SecurityError( f"Template {context} contains control characters which are not allowed" ) # Check for path traversal patterns value_lower = value.lower() for pattern in PATH_TRAVERSAL_PATTERNS: if pattern in value_lower: raise SecurityError( f"Template {context} contains path traversal pattern '{pattern}'" )
[docs] def url_encode_value(value: str) -> str: """ URL-encode a template value for safe inclusion in URLs. Args: value: The value to encode Returns: URL-encoded value """ return urllib.parse.quote(str(value), safe="")
[docs] def substitute_template( value: Any, runtime_params: Dict[str, Any], url_encode: bool = False, validate_security: bool = True ) -> Any: """ Recursively substitute {{variable}} placeholders in a value. Args: value: String, dict, list, or other value to process runtime_params: Dictionary of variable name -> value mappings url_encode: Whether to URL-encode substituted values validate_security: Whether to validate values for security issues Returns: Value with all {{variable}} placeholders substituted Raises: TemplateError: If a referenced variable is not found SecurityError: If a value contains dangerous patterns """ if isinstance(value, str): def replace_match(match): var_name = match.group(1) if var_name not in runtime_params: raise TemplateError(f"Missing template variable: {{{{{var_name}}}}}") replacement = str(runtime_params[var_name]) # Validate for security issues if validate_security: validate_template_value(replacement, context=f"variable '{var_name}'") # URL-encode if requested if url_encode: replacement = url_encode_value(replacement) return replacement return TEMPLATE_PATTERN.sub(replace_match, value) elif isinstance(value, dict): return { k: substitute_template(v, runtime_params, url_encode, validate_security) for k, v in value.items() } elif isinstance(value, list): return [ substitute_template(item, runtime_params, url_encode, validate_security) for item in value ] else: return value
[docs] def substitute_url(url_template: str, runtime_params: Dict[str, Any]) -> str: """ Substitute template variables in a URL with security validation. Template values are URL-encoded and validated for path traversal. Args: url_template: URL with {{variable}} placeholders runtime_params: Dictionary of variable name -> value mappings Returns: URL with all placeholders substituted Raises: TemplateError: If a variable is missing SecurityError: If URL is too long or contains dangerous patterns """ # Substitute with URL encoding and security validation url = substitute_template( url_template, runtime_params, url_encode=True, validate_security=True ) # Validate final URL length if len(url) > MAX_URL_LENGTH: raise SecurityError( f"URL exceeds maximum length of {MAX_URL_LENGTH} characters " f"(actual: {len(url)})" ) return url
[docs] def substitute_headers( headers: Dict[str, str], runtime_params: Dict[str, Any] ) -> Dict[str, str]: """ Substitute template variables in headers with security validation. Args: headers: Dictionary of header name -> value (with possible templates) runtime_params: Dictionary of variable name -> value mappings Returns: Headers with all placeholders substituted Raises: TemplateError: If a variable is missing SecurityError: If a value contains dangerous patterns """ return substitute_template( headers, runtime_params, url_encode=False, # Headers should not be URL-encoded validate_security=True )
[docs] def substitute_body( body: Any, runtime_params: Dict[str, Any] ) -> Any: """ Substitute template variables in request body. Args: body: Request body (dict, list, or string) with possible templates runtime_params: Dictionary of variable name -> value mappings Returns: Body with all placeholders substituted Raises: TemplateError: If a variable is missing """ return substitute_template( body, runtime_params, url_encode=False, validate_security=False # Body content is more permissive )