feat(fls): Java's view of the e-mail programs

The Java view now reads what FILIUS 2.14 keeps for its e-mail programs:
the server's domain, switch and bean accounts with their mailboxes, and the
accounts of /mailserver/konten.txt, which Java reads at every simulation
start and which so win over the bean list; the client's three folders and
address book, and its account from /konten.txt (no bean holds it).

javaEmail.ts ports the bean setters and both file readers
(stringZuListeBenutzerkonten, EmailKonto(String)); javaString.ts Java's
trim, split and StringTokenizer; javaFileSystem.ts the file lookup, now
shared with the DNS server, with the four non-ASCII units Java's
equalsIgnoreCase also takes for an ASCII letter (measured over the BMP).

Checked against Java on the corpus (the oracle dumps the e-mail state) and
on 43 account files read by EmailServer.kontenLaden and
EmailAnwendung.laden (EmailFileCases). Found: older files keep recipients
as strings in the typed lists; a getter copy of the sender is filled and
set back; one short server line makes Java throw for the whole file; a
message with an empty text is lost on reading; a broken last client line
empties the account.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 07:06:55 +00:00
co-authored by Claude Opus 5.5
parent c80d212c90
commit ec5523d7c7
22 changed files with 2900 additions and 94 deletions
+5 -40
View File
@@ -11,7 +11,9 @@
// - duplicates stay.
import type { DnsResourceRecord } from '@mdg-filius/core';
import { javaFileContent } from './javaFileSystem.js';
import { javaIpCheck, javaParseInt } from './javaIp.js';
import { javaTrim } from './javaString.js';
import type { FlsFileTree } from './systemSoftware.js';
/** A record as Java's ResourceRecord holds it (the getters). */
@@ -27,51 +29,14 @@ export const DNS_HOSTS_PATH = '/dns/hosts';
// EingabenUeberpruefung.musterDomain, including its quirks: labels start with
// a letter, labels after the first may hold a backslash, and the empty
// string is a valid domain.
const JAVA_DOMAIN = /^([a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?)*)?\.?$/;
export const JAVA_DOMAIN = /^([a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?)*)?\.?$/;
// EingabenUeberpruefung.musterIpAdresse.
const JAVA_IP_ADDRESS =
/^(0*([1-9]?[0-9]?|1[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))\.((0*(1?[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))\.){2}(0*(1?[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))$/;
/** `String.trim()`: every UTF-16 unit up to U+0020 off both ends. */
function javaTrim(text: string): string {
return text.replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, '');
}
/**
* The content of /dns/hosts as `Dateisystem.holeDatei` finds it: the first
* node in preorder whose path equals it ignoring case, and only if that node
* is a file. Undefined when there is none; a file without content reads as
* empty (Java would fail on it).
*/
/** The content of /dns/hosts as Java's DNS server finds it (see javaFileSystem.ts). */
export function javaHostsFileContent(root: FlsFileTree | undefined): string | undefined {
if (root === undefined) return undefined;
const found = firstInPreorder(root, '', (path) => javaEqualsIgnoreCase(path, DNS_HOSTS_PATH));
return found?.kind === 'file' ? (found.content ?? '') : undefined;
}
/** Preorder below the root; a node's path is "/" plus each name after the root's. */
function firstInPreorder(node: FlsFileTree, path: string, matches: (path: string) => boolean): FlsFileTree | undefined {
for (const child of node.children) {
const childPath = `${path}/${child.kind === 'other' ? child.className : String(child.name)}`;
if (matches(childPath)) return child;
const found = firstInPreorder(child, childPath, matches);
if (found !== undefined) return found;
}
return undefined;
}
/**
* `String.equalsIgnoreCase` against an ASCII path, unit by unit: equal after
* Character.toUpperCase, so the long s (U+017F) is an "s". Java also
* compares the lower case of both; for the letters of /dns/hosts that adds
* no unit (measured over the BMP).
*/
function javaEqualsIgnoreCase(text: string, asciiPath: string): boolean {
if (text.length !== asciiPath.length) return false;
for (let index = 0; index < text.length; index++) {
if (text[index]!.toUpperCase() !== asciiPath[index]!.toUpperCase()) return false;
}
return true;
return javaFileContent(root, DNS_HOSTS_PATH);
}
/** `DNSServer.leseRecordListe` on the content of /dns/hosts. */