# base-utils — Formation Recipe (FvW v8 §38)

**Module ID:** `base-utils`  
**Module name:** Core Utility Services  
**Lifecycle phase:** formation  
**Status:** INTENT ONLY. No code yet.  
**Date:** 2026-07-21  

---

## 1. Intent (the "why")
This module exists to provide foundational, stateless utility functions that the entire FreshCloud Mail system depends on. It solves the problem of scattered low-level operations (encryption, sanitization, date handling, resource management) by centralizing them in a single, well-tested, and secure abstraction layer. Its role is to:
- Eliminate code duplication across mail handlers
- Enforce security standards for data processing
- Provide predictable behavior across different PHP environments
- Serve as a dependency boundary for external libraries

---

## 2. User-facing behaviour
The following operations are supported through public methods:
- `sanitizeHtml()`: Clean untrusted HTML input to remove XSS risks
- `encryptData()`/`decryptData()`: Encrypt/decrypt strings using Xxtea
- `parseRfc2822Date()`: Convert email dates to Unix timestamps
- `getLocaleDate()`: Format dates/numbers per user locale
- `autoDetectCharset()`: Detect system character encoding
- `createResource()`: Track and auto-cleanup memory/resource handles

---

## 3. Public contract (data shapes + signatures)
```typescript
namespace FreshCloud\Mail\BaseUtils;

interface HtmlSanitizer {
  sanitizeHtml(html: string, allowedTags?: string[]): string;
}

interface CryptoService {
  encryptData(data: string, key: string): string;
  decryptData(ciphertext: string, key: string): string;
}

interface DateTimeHelper {
  parseRfc2822Date(dateString: string): number;
  getLocaleDate(timestamp: number, locale: string): string;
}

interface ResourceRegistry {
  createResource(): resource;
  registerResource(resource: resource): void;
  cleanup(): void;
}

interface LocaleService {
  autoDetectCharset(): string;
  getSupportedLocales(): string[];
}

// Main entry point
class BaseUtils {
  public static Html: HtmlSanitizer;
  public static Crypto: CryptoService;
  public static DateTime: DateTimeHelper;
  public static Resources: ResourceRegistry;
  public static Locale: LocaleService;
}
```

---

## 4. Persistence (what gets stored where)
- **No persistent storage** - All operations are stateless and ephemeral
- **ResourceRegistry**: In-memory registry only (cleans on shutdown)
- **Audit table**: Uses existing `email_audit` table for encoding/sanitization errors
- **Cache table**: Uses existing `email_drafts` table for locale caching (optional)

---

## 5. Dependencies (what this module uses, what uses this)
**Used by:**
- Message parser (`recipes/engine/message-parser`)
- Account manager (`recipes/engine/account-manager`)
- Cache service (`recipes/engine/cache`)
- Notification system (`recipes/engine/notifications`)

**Uses:**
- PHP extensions: `mbstring`, `libxml`
- External: `pocketbase` (for locale config)
- Internal: `freshcloud-mail-log` (errors)

---

## 6. Behaviour inventory (the N public methods)

| Method | Signature | Purpose | Side Effects | Errors |
|--------|-----------|---------|--------------|--------|
| `sanitizeHtml()` | `string(html, string[] = [])` | Clean XSS from untrusted HTML | None | `InvalidArgumentException`, `RuntimeException` |
| `encryptData()` | `string(data, key)` | Encrypt data with Xxtea | None | `InvalidArgumentException`, `RuntimeException` |
| `decryptData()` | `string(ciphertext, key)` | Decrypt Xxtea ciphertext | None | `InvalidArgumentException`, `RuntimeException` |
| `parseRfc2822Date()` | `int(dateString)` | Convert email date → timestamp | None | `InvalidArgumentException`, `RuntimeException` |
| `getLocaleDate()` | `string(timestamp, locale)` | Format date per user locale | None | `InvalidArgumentException` |
| `autoDetectCharset()` | `string()` | Detect system encoding | None | `RuntimeException` |
| `createResource()` | `resource()` | Allocates tracked resource | Registers resource | `RuntimeException` |
| `cleanup()` | `void()` | Releases all resources | Frees memory | None |

---

## 7. State machine
**State Components:**
- `ResourceRegistry` state: `{ resources: resource[] }`
- `LocaleService` state: `{ locales: string[] }`

**Transitions:**
| From | To | Trigger |
|------|----|---------|
| `Init` | `Ready` | Module load completes |
| `Ready` | `Error` | Invalid input received |
| `Ready` | `Operational` | Successful operation |
| `Operational` | `Cleanup` | Shutdown signal |

---

## 8. Side effects
| Action | Written | Read | Emitted |
|--------|---------|------|---------|
| Sanitization | `email_audit` table (XSS attempts) | HTML input | `error_xss_attempt` log |
| Encryption | None | Encryption key | `encryption_error` log |
| Date parsing | None | RFC2822 string | `date_parse_fail` log |
| Resource allocation | Memory (tracked) | Memory limits | `resource_created` metric |
| Locale lookup | Cache (optional) | `email_drafts` | `locale_lookup` metric |

---

## 9. Input validation
- **HTML sanitization**: Input must be UTF-8 string; max 1MB size
- **Encryption**: 16-byte key required; data must be UTF-8 string
- **Date parsing**: Strict RFC2822 format validation
- **Resources**: Memory limit enforcement (configurable via `memoryLimit` param)
- **Locale**: Must exist in supported locales list

---

## 10. Failure modes
| Failure | Handling | Caller Impact |
|---------|----------|---------------|
| Invalid HTML format | Throw `InvalidArgumentException` | Caller handles sanitization |
| Encryption key mismatch | Throw `RuntimeException` | Caller validates key |
| Unparseable date | Throw `RuntimeException` with `InvalidDateException` | Caller handles invalid dates |
| Memory limit exceeded | Throw `RuntimeException` | Caller implements retry logic |
| Resource allocation fail | Throw `RuntimeException` | Caller falls back to untracked resource |
| Locale not found | Throw `InvalidArgumentException` | Caller uses fallback locale |

---

## 11. User simulation
**Scenario 1: XSS Protection**
- **Given**: User submits comment containing `<script>alert(1)</script>`
- **When**: Comment is passed to `BaseUtils.Html.sanitizeHtml()`
- **Then**: HTML is stripped to safe format; attempt logged to `email_audit`

**Scenario 2: Email Encryption**
- **Given**: Email body needs encryption with user key
- **When**: `BaseUtils.Crypto.encryptData(body, userKey)` is called
- **Then**: Returns encrypted string; no sensitive data in logs

**Scenario 3: Date Display**
- **Given**: Timestamp `1714123456` and user locale `fr`
- **When**: `BaseUtils.DateTime.getLocaleDate(1714123456, 'fr')`
- **Then**: Returns `21 mai 2024 14:37` (French format)

**Scenario 4: Resource Leak Prevention**
- **Given**: Temporary file handle created during upload
- **When**: `BaseUtils.Resources.createResource()` called
- **Then**: Handle auto-deleted on script termination

**Scenario 5: Encoding Detection**
- **Given**: User paste contains accented characters
- **When**: `BaseUtils.Locale.autoDetectCharset()` called
- **Then`: Returns UTF-8 or detected encoding

**Scenario 6: Date Parsing**
- **Given**: Received email date: `Mon, 21 May 2024 14:37:36 +0000`
- **When**: `BaseUtils.DateTime.parseRfc2822Date("Mon, 21 May 2024 14:37:36 +0000")`
- **Then`: Returns `1714123456`

---

## 12. Reproducibility test
1. Create new PHP project with Composer
2. Require `freshcloud-mail/base-utils` package
3. Initialize `BaseUtils::Html.sanitizeHtml('<b>test</b>')`
4. Verify returns identical sanitized output across PHP 8.0-8.3
5. Run `BaseUtils::Crypto.encryptData('test', '16bytekey...')` and confirm decryption
6. Test RFC2822 dates with timezone offsets
7. Verify `BaseUtils::Resources.createResource()` creates valid resource handle
8. Confirm cleanup releases 100% registered resources
9. Validate locale outputs match expected Unicode formats

---

## 13. Substitutability (FvW v8 §37.4)
To be a drop-in replacement, an implementation MUST:
- Provide identical public method signatures
- Guarantee RFC2822 date parsing compatibility
- Implement equivalent XSS sanitization with same tag allowlist
- Support Xxtea encryption with identical output
- Maintain resource cleanup guarantees during shutdown
- Support UTF-8/UTF-16 input encoding
- Log errors to standard FreshCloud channels (`email_audit` table)

---

## 14. Invariants (the 5 things that must always hold)
1. **Statelessness**: No user data stored between method calls
2. **Encoding Agnosticism**: All operations must handle UTF-8/UTF-16 inputs
3. **Memory Safety**: Resources registered with `ResourceRegistry` must be freed
4. **Error Logging**: All failures logged to `email_audit` with severity
5. **Deterministic Output**: Same input always produces same sanitized/encrypted output

---

## 15. Sweep fields applied (the 14 design intent fields)

### Sweep 1: Multi-user + PocketBase
- **user_scoping**: Stateless - no user data in any method  
  *(Design: No user parameters in interfaces)*
- **pb_integration**: Only for locale cache in `email_drafts` table  
  *(Design: `getSupportedLocales()` reads from PocketBase config)*
- **auth_boundary**: No auth required - called by any module  
  *(Design: No authentication checks in methods)*
- **state_isolation**: Complete - all methods operate on inputs only  
  *(Design: No session data or global state)*
- **logout_behaviour**: No action required (stateless)  
  *(Design: No session cleanup needed)*
- **multi_account**: No impact (universal helpers)  
  *(Design: Account-agnostic operations)*

### Sweep 2: World-class Abilities
- **connection_pool**: N/A (stateless helpers)  
  *(Design: No connection state)*
- **offline_cache**: ✓ DateTime caching via `email_drafts` table  
  *(Design: `getLocaleDate()` caches timestamp conversions)*
- **sieve_integration**: N/A (no Sieve involvement)  
  *(Design: Not used in filter engine)*
- **audit_logging**: ✓ XSS/encryption errors logged  
  *(Design: `sanitizeHtml()` logs to `email_audit`)*
- **ux_primitives**: ✓ Locale-aware date formatting  
  *(Design: `getLocaleDate()` uses user locale)*

### Sweep 3: Migration + Observability + Tests
- **migration**: ✓ Deterministic operations (no migration concerns)  
  *(Design: All functions are input → output only)*
- **observability**: ✓ XSS attempt counters, slow locale lookups  
  *(Design: Metrics: `sanitize_html_errors_total`, `locale_lookup_duration`)*
- **tests**: ✓ 6+ PHPUnit tests with XSS payloads  
  *(Design: Test files: `tests/base-utils/SecurityTest.php`)*

---

## 16. Cross-references
- **§11**: Module constitution rules (this module is constitutional per §11.9)
- **§37**: Fragment requirements (this document is the fragment §37.2 contract)
- **§38**: Formation phase rules (this document)
- **§19**: MailSo reference (historical only, no AGPL lineage)
- **Dependent modules**: Message parser, Account manager, Cache service
- **Cross-module**: `freshcloud-mail-log` integration

---

## 17. What we are intentionally NOT doing (deferred to future)
1. **No HTML rendering** - Delegated to UI layer (React)
2. **No IMAP/SMTP protocols** - Handled by `message-parser` module
3. **No password handling** - Decoupled into `auth` module
4. **No caching system** - Delegated to `cache` module
5. **No internationalization files** - Uses PocketBase for locales
6. **No AGPLv3 code** - Clean-room implementation only
7. **No legacy encoding support** - UTF-8/UTF-16 only
7. **No cryptographic key management** - Keys provided by caller
8. **No filesystem operations** - ResourceRegistry is memory-only
9. **No third-party HTML parsers** - Uses DOMDocument with custom allowlists
10. **No locale files** - Uses PocketBase user preferences
11. **No configuration system** - Uses environment variables
12. **No performance benchmarks** - Benchmarking deferred to optimization phase
13. **No compression** - Delegated to `compression` module
14. **No URL validation** - Will be in `validation` module

*MailSo had 7 PHP files. We will have 4:*
- `HtmlUtils.php` (sanitization)
- `CryptoUtils.php` (Xxtea)
- `DateTimeUtils.php` (dates)
- `ResourceRegistry.php` (resource management)
*Reason: Simpler architecture, no locale files needed, cleaner separation of concerns*