fix(fls): follow Java's formats, not Java's errors

Java stays the reference for the .fls format, but its errors are no longer
copied. The Java oracle still measures Java as it is; each deliberate
departure is named in the tests with its reason (JAVA_BUGS) and must really
depart. The corpus - what Java itself writes - still reads exactly as in
Java.

- /dns/hosts: a label may start with a digit, holds no backslash, and the
  last one is not all digits (RFC 1123, RFC 3696); A, MX and NS are read in
  any case (RFC 4343); a negative TTL is invalid, so 3600 (RFC 2181). Also
  for the e-mail server's domain.
- /mailserver/konten.txt: empty fields count (Java's StringTokenizer skips
  them and shifts the rest, so an empty password breaks the account); a
  short line alone is left out, with a warning (Java throws for the whole
  file); a message keeps an empty text or subject (Java's split loses it);
  a ";" in an unescaped subject stays in the message; CRLF lines.
- /konten.txt: the last complete line is the account, also with an empty
  address (Java takes the last line and loses the account); CRLF lines.
- Static DHCP assignments: a broken entry alone is left out (Java loses
  every entry after it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 07:20:52 +00:00
co-authored by Claude Opus 5.5
parent ec5523d7c7
commit 8f9930f1ff
10 changed files with 319 additions and 160 deletions
+14 -14
View File
@@ -27,19 +27,18 @@
// not know Java's constructors and sees a placeholder per path; they are
// merged here.
import { JAVA_DOMAIN } from './dnsHosts.js';
import { isDomainName } from './dnsHosts.js';
import {
EMAIL_CLIENT_FILE,
EMAIL_SERVER_FILE,
javaContacts,
javaEmailAccounts,
javaEmails,
readJavaEmailClientFile,
readJavaEmailServerFile,
readEmailClientFile,
readEmailServerFile,
type JavaContact,
type JavaEmail,
type JavaEmailAccount,
type JavaFailure,
} from './javaEmail.js';
import { javaFileContent } from './javaFileSystem.js';
import {
@@ -151,16 +150,16 @@ export interface FlsApplication {
maxPeers?: number | null;
/** Firewall: the personal firewall. */
firewall?: FlsFirewall;
/** EmailServer: its domain (Java keeps "filius.de" for one its pattern rejects). */
/** EmailServer: its domain ("filius.de" for one that is not a domain name, as Java's setter keeps it). */
mailDomain?: string;
/** EmailServer: the accounts of the bean list. */
beanAccounts?: JavaEmailAccount[];
/**
* EmailServer: the accounts of /mailserver/konten.txt, which Java reads at
* every simulation start — null without the file (Java then starts with
* none), a failure where Java throws.
* none).
*/
fileAccounts?: JavaEmailAccount[] | JavaFailure | null;
fileAccounts?: JavaEmailAccount[] | null;
/** EmailAnwendung: its folders (Java's empfangene, gesendete, erstellte Nachrichten). */
received?: JavaEmail[];
sent?: JavaEmail[];
@@ -313,10 +312,10 @@ function applications(map: JavaValue | undefined, root: FlsFileTree, warn: Warn)
case 'filius.software.email.EmailServer': {
entry.isActive = boolean(at('aktiv'), false, 'EmailServer aktiv', warn);
const domain = optionalString(at('mailDomain'), 'EmailServer mailDomain', warn);
entry.mailDomain = typeof domain === 'string' && JAVA_DOMAIN.test(domain) ? domain : 'filius.de';
entry.mailDomain = typeof domain === 'string' && isDomainName(domain) ? domain : 'filius.de';
entry.beanAccounts = javaEmailAccounts(at('listeBenutzerkonten'), warn);
const file = javaFileContent(root, EMAIL_SERVER_FILE);
entry.fileAccounts = file === undefined ? null : readJavaEmailServerFile(file);
entry.fileAccounts = file === undefined ? null : readEmailServerFile(file, warn);
break;
}
case 'filius.software.email.EmailAnwendung': {
@@ -325,7 +324,7 @@ function applications(map: JavaValue | undefined, root: FlsFileTree, warn: Warn)
entry.drafts = javaEmails(at('erstellteNachrichten'), warn);
entry.addressBook = javaContacts(at('adressbuch'), warn);
const file = javaFileContent(root, EMAIL_CLIENT_FILE);
entry.fileAccount = file === undefined ? null : readJavaEmailClientFile(file);
entry.fileAccount = file === undefined ? null : readEmailClientFile(file, warn);
break;
}
}
@@ -406,16 +405,17 @@ function dhcpServer(server: JavaValue | undefined, warn: Warn): FlsDhcpServer {
/**
* setStaticAssignedAddresses: each entry split at whitespace (StringUtils.split),
* MAC and IP from the first two parts. An entry with fewer throws inside the
* loop — XMLDecoder swallows it, and the entries after it are lost.
* MAC and IP from the first two parts. An entry with fewer makes Java throw
* inside the loop — XMLDecoder swallows it, and the entries after it are
* lost. That error is not copied: such an entry alone is left out.
*/
function staticAssignments(list: JavaValue | undefined, warn: Warn): { mac: string; ip: string }[] {
const result: { mac: string; ip: string }[] = [];
for (const entry of listItems(list)) {
const parts = typeof entry === 'string' ? javaWhitespaceSplit(entry) : [];
if (parts.length < 2) {
warn(`DHCPServer: static assignment ${describe(entry)} has no MAC and IP; Java stops reading here`);
break;
warn(`DHCPServer: static assignment ${describe(entry)} has no MAC and IP, left out`);
continue;
}
result.push({ mac: parts[0]!, ip: parts[1]! });
}