# mime-parser — Formation Recipe (FvW v8 §38)

**Module ID:** `mime-parser`
**Module name:** FreshCloud MIME Parser
**Lifecycle phase:** formation
**Status:** INTENT ONLY. No code yet.
**Date:** 2026-07-21

---

## 1. Intent (the "why")
- Extract structured data from raw MIME email streams for FreshCloud Mail
- Enable message rendering (body text/HTML), attachment handling, and metadata extraction
- Provide a secure, isolated parsing layer for mailbox operations
- Support offline access through cached parsing results
- Enable search indexing and message analytics via parsed content

---

## 2. User-facing behaviour
- Parse raw MIME strings/files into `MessageObject` structures
- Extract headers (From/To/CC/Subject/Date)
- Retrieve text and HTML body content with proper encoding
- List attachments with metadata (filename, size, MIME type, content ID)
- Traverse MIME part hierarchies for complex messages
- Cache parsed results for subsequent fast access

---

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

interface MessageObject {
  public function getHeaders(): HeaderCollection;
  public function getSubject(): string;
  public function getFrom(): Address;
  public function getTo(): AddressCollection;
  public function getBodyText(): ?string;
  public function getBodyHtml(): ?string;
  public function getAttachments(): PartCollection;
  public function getDate(): DateTime;
}

interface HeaderCollection {
  public function get(string $name): Header;
  public function getAll(): array;
}

interface PartCollection {
  public function count(): int;
  public function get(int $index): Part;
  public function findByContentId(string $cid): ?Part;
}

interface Part {
  public function getFilename(): string;
  public function getSize(): int;
  public function getMimeType(): string;
  public function getContent(): string;
  public function isAttachment(): bool;
}

interface Parser {
  public function parseFromString(string $mimeContent): MessageObject;
  public function parseFromFile(string $filepath): MessageObject;
  public function getCachedParts(string $messageId): ?PartCollection;
  public function storeCachedParts(string $messageId, PartCollection $parts): void;
}
```

---

## 4. Persistence (what gets stored where)
- **Disk cache**: 
  - Path: `/storage/user_{id}/mime_cache/{message_id}.cache`
  - Content: Serialized PartCollection for quick reload
  - Encryption: AES-256-GCM with user-specific key
- **PocketBase `email_messages`**:
  - Columns: `body_text`, `body_html`, `attachment_list` (JSON array)
  - Updates: On each parse via bridge layer
- **No database**: Parser itself writes only to cache files (PB writes handled by bridge)

---

## 5. Dependencies (what this module uses, what uses this)
- **Uses**:
  - `FreshCloud\Mail\Log\Logger` (audit logging)
  - `FreshCloud\Base\Base64Encoder` (attachment decoding)
  - `FreshCloud\Security\Sanitizer` (XSS protection for HTML)
  - PHP `SplFileObject` for file parsing
- **Used by**:
  - `MailboxIndex` (for message metadata)
  - `MessageViewer` (for content rendering)
  - `AttachmentDownloader` (for file serving)
  - `SearchIndexer` (for content indexing)

---

## 6. Behaviour inventory (the N public methods)
| Method | Purpose | Side Effects | Errors |
|--------|---------|--------------|--------|
| `parseFromString()` | Parse MIME from string memory | Writes cache, emits logs | `ParseException`, `TimeoutException` |
| `parseFromFile()` | Parse MIME from disk file | Reads file, writes cache | `IOException`, `ParseException` |
| `getCachedParts()` | Retrieve cached MIME parts | Reads cache file | `CacheException`, `EncryptionException` |
| `storeCachedParts()` | Persist parsed parts | Writes cache, encrypts data | `IOException`, `EncryptionException` |
| `sanitizeHtml()` | Clean HTML content | Logs sanitization events | `SanitizationException` |

---

## 7. State machine
```
[Idle] --(parse)--> [Parsing]
[Parsing] --(success)--> [Parsed]
[Parsing] --(failure)--> [Error]
[Parsed] --(cache miss)--> [CacheRequest]
[CacheRequest] --(hit)--> [Cached]
[CacheRequest] --(miss)--> [Reparse]
[Reparse] --(success)--> [Cached]
```

---

## 8. Side effects
- **Writes**:
  - Disk cache files (1-2MB per message)
  - Audit logs for parsing events
- **Reads**:
  - Raw MIME content (strings/files)
  - Existing cache files
- **Emits**:
  - `mime_parsed` metric with duration
  - `mime_cache_hit` counter
  - `attachment_processed` event for each attachment

---

## 9. Input validation
- MIME must start with valid header block (no binary garbage)
- Content-Type boundaries must match multipart sections
- Encoding must be supported (UTF-8, quoted-printable, base64)
- Maximum message size: 25MB (configurable)
- Timeout: 30 seconds per message (configurable)

---

## 10. Failure modes
| Failure | Handler | Caller Sees |
|---------|---------|-------------|
| Malformed MIME | Throw `ParseException` | Exception with position pointer |
| Timeout | Throw `TimeoutException` | Exception with elapsed time |
| Cache write fail | Retry 3x then throw `CacheException` | Exception with retry log |
| Attachment too large | Skip attachment, log warning | Missing attachment in output |
| HTML sanitization fail | Strip tags, log error | Sanitized HTML (no tags) |

---

## 11. User simulation
**Scenario 1: Simple Text Email**  
Given: Raw MIME with plain text body  
When: `parseFromString()` called  
Then: `getBodyText()` returns content, `getBodyHtml()` returns null  

**Scenario 2: Multi-part Message**  
Given: MIME with text/alternative parts  
When: `parseFromString()` called  
Then: Both text and HTML bodies available via respective methods  

**Scenario 3: Attachment Processing**  
Given: MIME with base64 PDF attachment  
When: `parseFromFile()` called  
Then: `getAttachments()` returns Part with decoded content  

**Scenario 4: Cache Hit**  
Given: Previous parse result cached  
When: `getCachedParts()` called  
Then: Returns unserialized PartCollection instantly  

**Scenario 5: Malformed Boundary**  
Given: MIME with broken multipart boundary  
When: Parser attempts parsing  
Then: Throws `ParseException` at line 42  

---

## 12. Reproducibility test
1. Create `FreshCloud\Mail\MimeParser\Parser` class  
2. Implement `parseFromString()` with RFC 2045 compliance  
3. Add header extraction logic  
4. Implement multipart boundary detection  
5. Add body encoding handlers  
6. Create disk cache serializer/deserializer  
7. Add security sanitizer for HTML  
8. Write unit tests with Greenmail fixtures  
9. Validate with malformed MIME samples  

---

## 13. Substitutability (FvW v8 §37.4)
- Must implement `Parser` interface exactly  
- Must throw same exception types (`ParseException`, `TimeoutException`)  
- Must maintain caching behavior (cache key = message_id)  
- Must provide XSS-safe HTML output  
- Must support same input MIME formats  

---

## 14. Invariants (the 5 things that must always hold)
1. No direct I/O outside cache files (PB writes handled by bridge)  
2. Always sanitize HTML output before returning  
3. Attachment content never stored in memory >100KB at once  
4. Cache files encrypted with user-specific keys  
5. Never expose raw MIME content to calling modules  

---

## 15. Sweep fields applied (the 14 design intent fields)
### Sweep 1: Multi-user + PocketBase
- **user_scoping**: parser methods accept user_id for cache encryption  
- **pb_integration**: updates `email_messages` body columns via bridge  
- **auth_boundary**: no auth (caller provides user_id)  
- **state_isolation**: cache keyed by (user_id, message_id)  
- **logout_behaviour**: cache expires on user session end  
- **multi_account**: same interface for all user accounts  

### Sweep 2: World-class Abilities
- **connection_pool**: N/A (in-memory only)  
- **offline_cache**: disk cache with encrypted part storage  
- **sieve_integration**: N/A (content extraction only)  
- **audit_logging**: emits `mime_parsed` events to logger  
- **ux_primitives**: outputs `MessageObject` with UI-ready fields  

### Sweep 3: Migration + Observability + Tests
- **migration**: handles RFC 2045-2049 (historical MIME)  
- **observability**: metrics for parse duration/cache hits  
- **tests**:PHPUnit with .eml fixtures + malformed MIME samples  

---

## 16. Cross-references
- **FvW §11**: Constitutional module requirements  
- **FvW §37**: Public contract standards (this module follows §37.4)  
- **FvW §38**: Formation phase documentation  
- **§19**: MailSo as historical reference (clean-room rebuild)  
- **Dependent modules**: `MailboxIndex`, `MessageViewer`, `AttachmentDownloader`  

---

## 17. What we are intentionally NOT doing (deferred to future)
1. Content preview generation (defer to UI layer)  
2. Message reconstruction back to raw format  
3. Advanced charset detection (UTF-8 only for v1.0)  
4. Content-ID resolution (handled by attachment layer)  
5. Embedded media parsing (defer to viewer)  
6. Sieve filtering rules (content-only)  
7. IMAP/SMTP protocol handling (lower layer)  
8. PocketBase direct writes (bridge layer)  
9. MIME signature verification (defer to security module)  
10. Compressed content decompression (base64 only)  
11. Message threading logic (index layer)  
12. RFC 2231 encoded filename handling (ASCII filenames only)  
13. RTF/alternative format support (text/html only)  
14. MailSo's HeaderParser class (simplified to HeaderCollection)  
15. Attachment content conversion (serve as-is)