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
+1 -23
View File
@@ -8,8 +8,7 @@ import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import type { FlsFileTree } from '../src/index.js';
import { dnsRecordsOf, javaHostsFileContent, readJavaDnsHosts } from '../src/dnsHosts.js';
import { dnsRecordsOf, readJavaDnsHosts } from '../src/dnsHosts.js';
const here = dirname(fileURLToPath(import.meta.url));
type Row = [string, string, number, string];
@@ -186,24 +185,3 @@ describe('the records Java answers', () => {
expect(warnings).toEqual(["DNS record 'www.filius.de A .1.2.3' has no usable IPv4 address, left out (it stays in /dns/hosts)"]);
});
});
describe('javaHostsFileContent', () => {
const file = (name: string, content: string | null): FlsFileTree => ({ kind: 'file', name, type: 'text', content, children: [] });
const directory = (name: string, ...children: FlsFileTree[]): FlsFileTree => ({ kind: 'directory', name, children });
it('finds /dns/hosts ignoring case, as Java does', () => {
expect(javaHostsFileContent(directory('root', directory('DNS', file('Hosts', 'x'))))).toBe('x');
expect(javaHostsFileContent(directory('root', directory('dns', file('ho\u017Fts', 'long s'))))).toBe('long s');
});
it('takes the first match in preorder, and nothing when that is a directory', () => {
expect(javaHostsFileContent(directory('root', directory('dns', file('hosts', 'first')), directory('DNS', file('hosts', 'second'))))).toBe('first');
expect(javaHostsFileContent(directory('root', directory('dns', directory('hosts'), file('HOSTS', 'later'))))).toBeUndefined();
});
it('is undefined without the file, and empty for a file without content', () => {
expect(javaHostsFileContent(undefined)).toBeUndefined();
expect(javaHostsFileContent(directory('root', file('hosts', 'x')))).toBeUndefined();
expect(javaHostsFileContent(directory('root', directory('dns', file('hosts', null))))).toBe('');
});
});
+16
View File
@@ -0,0 +1,16 @@
mail.gmx.de;mail.gmx.de;110;25;herbert;herbert1234;Schmidt;Herbert;herbert@gmx.de
mail.gmx.de;mail.gmx.de;110;25;herbert;herbert1234;Schmidt;Herbert;herbert@gmx.de\u000A
a;a;110;25;first;p;L;F;first@x\u000Ab;b;110;25;second;p;L;F;second@x\u000A
a;a;110;25;first;p;L;F;first@x\u000Abroken\u000A
\u000A
\u000A\u000A\u000A
a;b;110;25;u;p;L;F;\u000A
a;b;110;25;u;p;L;F;u@x;extra\u000A
a;b;110;25;u;p;L;F;u@x\u000D\u000A
a;;110;25;u;p;;;u@x\u000A
a;b;110;25;u;p;L;F;u@x\u000A\u000A\u000A
\u000Aa;b;110;25;u;p;L;F;u@x
a ; b ;x;y; u ; p ; L ; F ; u@x \u000A
;;;;;;;;
;;;;;;;;x
+220
View File
@@ -0,0 +1,220 @@
[
[
"mail.gmx.de;mail.gmx.de;110;25;herbert;herbert1234;Schmidt;Herbert;herbert@gmx.de",
{
"benutzername": "herbert",
"passwort": "herbert1234",
"nachname": "Schmidt",
"vorname": "Herbert",
"emailAdresse": "herbert@gmx.de",
"pop3server": "mail.gmx.de",
"pop3port": "110",
"smtpserver": "mail.gmx.de",
"smtpport": "25",
"nachrichten": []
}
],
[
"mail.gmx.de;mail.gmx.de;110;25;herbert;herbert1234;Schmidt;Herbert;herbert@gmx.de\\u000A",
{
"benutzername": "herbert",
"passwort": "herbert1234",
"nachname": "Schmidt",
"vorname": "Herbert",
"emailAdresse": "herbert@gmx.de",
"pop3server": "mail.gmx.de",
"pop3port": "110",
"smtpserver": "mail.gmx.de",
"smtpport": "25",
"nachrichten": []
}
],
[
"a;a;110;25;first;p;L;F;first@x\\u000Ab;b;110;25;second;p;L;F;second@x\\u000A",
{
"benutzername": "second",
"passwort": "p",
"nachname": "L",
"vorname": "F",
"emailAdresse": "second@x",
"pop3server": "b",
"pop3port": "110",
"smtpserver": "b",
"smtpport": "25",
"nachrichten": []
}
],
[
"a;a;110;25;first;p;L;F;first@x\\u000Abroken\\u000A",
{
"benutzername": null,
"passwort": null,
"nachname": "",
"vorname": "",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": []
}
],
[
"",
{
"benutzername": null,
"passwort": null,
"nachname": "",
"vorname": "",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": []
}
],
[
"\\u000A",
null
],
[
"\\u000A\\u000A\\u000A",
null
],
[
"a;b;110;25;u;p;L;F;\\u000A",
{
"benutzername": null,
"passwort": null,
"nachname": "",
"vorname": "",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": []
}
],
[
"a;b;110;25;u;p;L;F;u@x;extra\\u000A",
{
"benutzername": "u",
"passwort": "p",
"nachname": "L",
"vorname": "F",
"emailAdresse": "u@x",
"pop3server": "a",
"pop3port": "110",
"smtpserver": "b",
"smtpport": "25",
"nachrichten": []
}
],
[
"a;b;110;25;u;p;L;F;u@x\\u000D\\u000A",
{
"benutzername": "u",
"passwort": "p",
"nachname": "L",
"vorname": "F",
"emailAdresse": "u@x\r",
"pop3server": "a",
"pop3port": "110",
"smtpserver": "b",
"smtpport": "25",
"nachrichten": []
}
],
[
"a;;110;25;u;p;;;u@x\\u000A",
{
"benutzername": "u",
"passwort": "p",
"nachname": "",
"vorname": "",
"emailAdresse": "u@x",
"pop3server": "a",
"pop3port": "110",
"smtpserver": "",
"smtpport": "25",
"nachrichten": []
}
],
[
"a;b;110;25;u;p;L;F;u@x\\u000A\\u000A\\u000A",
{
"benutzername": "u",
"passwort": "p",
"nachname": "L",
"vorname": "F",
"emailAdresse": "u@x",
"pop3server": "a",
"pop3port": "110",
"smtpserver": "b",
"smtpport": "25",
"nachrichten": []
}
],
[
"\\u000Aa;b;110;25;u;p;L;F;u@x",
{
"benutzername": "u",
"passwort": "p",
"nachname": "L",
"vorname": "F",
"emailAdresse": "u@x",
"pop3server": "a",
"pop3port": "110",
"smtpserver": "b",
"smtpport": "25",
"nachrichten": []
}
],
[
" a ; b ;x;y; u ; p ; L ; F ; u@x \\u000A",
{
"benutzername": " u ",
"passwort": " p ",
"nachname": " L ",
"vorname": " F ",
"emailAdresse": " u@x ",
"pop3server": " a ",
"pop3port": "x",
"smtpserver": " b ",
"smtpport": "y",
"nachrichten": []
}
],
[
";;;;;;;;",
{
"benutzername": null,
"passwort": null,
"nachname": "",
"vorname": "",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": []
}
],
[
";;;;;;;;x",
{
"benutzername": "",
"passwort": "",
"nachname": "",
"vorname": "",
"emailAdresse": "x",
"pop3server": "",
"pop3port": "",
"smtpserver": "",
"smtpport": "",
"nachrichten": []
}
]
]
+27
View File
@@ -0,0 +1,27 @@
bob;filius.de;bob;Nachname;Vorname;\u000A
bob;filius.de;bob;Nachname;Vorname;\u000Aalice;filius.de;alice;A;B;\u000A
bob;filius.de;bob;Nachname;Vorname
bob;;bob;Nachname;Vorname;\u000A
bob;filius.de;bob;Nachname\u000A
bob;filius.de;bob;Nachname;Vorname;\u000Abroken\u000A
bob ;filius.de; bob ;Nachname;Vorname;\u000A
bob;filius.de;bob;Nachname;Vorname\u000D\u000A
\u000A\u000A
bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$Hi Bob!$Hi BoB!|\u000A
bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$Hi Bob!$Hi BoB!|#Alice <alice@senior.de>$bob@filius.de$$$$Hi Bob!$Hi BoB!|\u000A
bob;filius.de;bob;Nachname;Vorname;Alice <alice@senior.de>$bob@filius.de$$$$Hi$Text\u000A
bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$Hi$\u000A
bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$$\u000A
bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$Hi $5$Text\u000A
bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$c@z$d@w$1234$Hi$Text\u000A
bob;filius.de;bob;Nachname;Vorname;#a@x$b@y, ,c@z,$$$$Hi$Text\u000A
bob;filius.de;bob;Nachname;Vorname;#Name <a@x$b@y$$$$Hi$Text\u000A
bob;filius.de;bob;Nachname;Vorname;#<a@x>$Bob <b@y> , <c@z>$$$$Hi$Text\u000A
bob;filius.de;bob;Nachname;Vorname;#Only Name$b@y$$$$Hi$Text\u000A
bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$l1||l2&&00124&&x&&00036&&y&&00059&&z&&00035&&w\u000A
bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$&&00124&&|\u000A
bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$Text;extra\u000A
bob;filius.de;bob;Nachname;Vorname;##a@x$b@y$$$$Hi$Text##\u000A
bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$Text\u000D\u000A
bob;filius.de;bob;Nachname;Vorname;#\u001Fa@x $b@y$$$$Hi$Text\u000A
+740
View File
@@ -0,0 +1,740 @@
[
[
"bob;filius.de;bob;Nachname;Vorname;\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": []
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;\\u000Aalice;filius.de;alice;A;B;\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": []
},
{
"benutzername": "alice",
"passwort": "alice",
"nachname": "A",
"vorname": "B",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": []
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": []
}
]
],
[
"bob;;bob;Nachname;Vorname;\\u000A",
"!NoSuchElementException"
],
[
"bob;filius.de;bob;Nachname\\u000A",
"!NoSuchElementException"
],
[
"bob;filius.de;bob;Nachname;Vorname;\\u000Abroken\\u000A",
"!NoSuchElementException"
],
[
" bob ;filius.de; bob ;Nachname;Vorname;\\u000A",
[
{
"benutzername": " bob ",
"passwort": " bob ",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": []
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname\\u000D\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname\r",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": []
}
]
],
[
"",
[]
],
[
"\\u000A\\u000A",
[]
],
[
"bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$Hi Bob!$Hi BoB!|\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
"Alice",
"alice@senior.de"
],
"empfaenger": [
[
null,
"bob@filius.de"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi Bob!",
"text": "Hi BoB!\n",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$Hi Bob!$Hi BoB!|#Alice <alice@senior.de>$bob@filius.de$$$$Hi Bob!$Hi BoB!|\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
"Alice",
"alice@senior.de"
],
"empfaenger": [
[
null,
"bob@filius.de"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi Bob!",
"text": "Hi BoB!\n",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
},
{
"absender": [
"Alice",
"alice@senior.de"
],
"empfaenger": [
[
null,
"bob@filius.de"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi Bob!",
"text": "Hi BoB!\n",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;Alice <alice@senior.de>$bob@filius.de$$$$Hi$Text\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
"Alice",
"alice@senior.de"
],
"empfaenger": [
[
null,
"bob@filius.de"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi",
"text": "Text",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$Hi$\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": []
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$$\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": []
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#Alice <alice@senior.de>$bob@filius.de$$$$Hi $5$Text\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": []
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$c@z$d@w$1234$Hi$Text\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
null,
"a@x"
],
"empfaenger": [
[
null,
"b@y"
]
],
"cc": [
[
null,
"c@z"
]
],
"bcc": [
[
null,
"d@w"
]
],
"betreff": "Hi",
"text": "Text",
"dateReceived": "1234",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#a@x$b@y, ,c@z,$$$$Hi$Text\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
null,
"a@x"
],
"empfaenger": [
[
null,
"b@y"
],
[
null,
"c@z"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi",
"text": "Text",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#Name <a@x$b@y$$$$Hi$Text\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
"Name <a@x",
null
],
"empfaenger": [
[
null,
"b@y"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi",
"text": "Text",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#<a@x>$Bob <b@y> , <c@z>$$$$Hi$Text\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
null,
"a@x"
],
"empfaenger": [
[
"Bob",
"b@y"
],
[
null,
"c@z"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi",
"text": "Text",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#Only Name$b@y$$$$Hi$Text\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
"Only Name",
null
],
"empfaenger": [
[
null,
"b@y"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi",
"text": "Text",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$l1||l2&&00124&&x&&00036&&y&&00059&&z&&00035&&w\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
null,
"a@x"
],
"empfaenger": [
[
null,
"b@y"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi",
"text": "l1\n\nl2|x$y;z#w",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$&&00124&&|\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
null,
"a@x"
],
"empfaenger": [
[
null,
"b@y"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi",
"text": "|\n",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$Text;extra\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
null,
"a@x"
],
"empfaenger": [
[
null,
"b@y"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi",
"text": "Text",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;##a@x$b@y$$$$Hi$Text##\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
null,
"a@x"
],
"empfaenger": [
[
null,
"b@y"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi",
"text": "Text",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$Text\\u000D\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
null,
"a@x"
],
"empfaenger": [
[
null,
"b@y"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi",
"text": "Text\r",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
],
[
"bob;filius.de;bob;Nachname;Vorname;#\\u001Fa@x $b@y$$$$Hi$Text\\u000A",
[
{
"benutzername": "bob",
"passwort": "bob",
"nachname": "Nachname",
"vorname": "Vorname",
"emailAdresse": null,
"pop3server": null,
"pop3port": null,
"smtpserver": null,
"smtpport": null,
"nachrichten": [
{
"absender": [
null,
"a@x"
],
"empfaenger": [
[
null,
"b@y"
]
],
"cc": [],
"bcc": [],
"betreff": "Hi",
"text": "Text",
"dateReceived": "",
"neu": true,
"delete": false,
"versendet": false
}
]
}
]
]
]
+55
View File
@@ -0,0 +1,55 @@
// Java's readers of the e-mail account files: every case in
// email-server.cases.txt and email-client.cases.txt was read by Java
// (EmailServer.kontenLaden, EmailAnwendung.laden; *.java.json,
// tools/java-oracle) and must come out the same here.
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { addressEntry, readJavaEmailClientFile, readJavaEmailServerFile } from '../src/javaEmail.js';
import { javaEmailAccount } from './javaEmailNames.js';
const here = dirname(fileURLToPath(import.meta.url));
/** The cases file's escapes: a backslash, `u` and four hex digits for one UTF-16 unit. */
function unescape(line: string): string {
return line.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex: string) => String.fromCharCode(Number.parseInt(hex, 16)));
}
function measured(kind: 'server' | 'client'): [string, unknown][] {
const cases = JSON.parse(readFileSync(join(here, `email-${kind}.java.json`), 'utf8')) as [string, unknown][];
const lines = readFileSync(join(here, `email-${kind}.cases.txt`), 'ascii').split('\n').slice(0, -1);
it('covers every line of the cases file', () => {
expect(cases.map(([line]) => line)).toEqual(lines);
});
return cases;
}
describe('readJavaEmailServerFile against Java', () => {
for (const [line, expected] of measured('server')) {
it(JSON.stringify(line), () => {
const accounts = readJavaEmailServerFile(unescape(line));
expect(Array.isArray(accounts) ? accounts.map(javaEmailAccount) : `!${accounts.exception}`).toEqual(expected);
});
}
});
describe('readJavaEmailClientFile against Java', () => {
for (const [line, expected] of measured('client')) {
it(JSON.stringify(line), () => {
const account = readJavaEmailClientFile(unescape(line));
expect(account === null ? null : javaEmailAccount(account)).toEqual(expected);
});
}
});
describe('addressEntry', () => {
it('reads a name and an address, an address alone, or a name alone', () => {
expect(addressEntry(' Alice <alice@senior.de> ')).toEqual({ name: 'Alice', mailAddress: 'alice@senior.de' });
expect(addressEntry('<alice@senior.de>')).toEqual({ name: null, mailAddress: 'alice@senior.de' });
expect(addressEntry('alice@senior.de')).toEqual({ name: null, mailAddress: 'alice@senior.de' });
expect(addressEntry('Alice <alice@senior.de')).toEqual({ name: 'Alice <alice@senior.de', mailAddress: null });
expect(addressEntry('Alice')).toEqual({ name: 'Alice', mailAddress: null });
});
});
+37
View File
@@ -0,0 +1,37 @@
// The e-mail beans under Java's property names, as FlsOracle.java and
// EmailFileCases.java write them.
import type { JavaEmail, JavaEmailAccount, JavaRecipient } from '../src/index.js';
export function javaEmailAccount(account: JavaEmailAccount) {
return {
benutzername: account.username,
passwort: account.password,
nachname: account.lastName,
vorname: account.firstName,
emailAdresse: account.emailAddress,
pop3server: account.pop3Server,
pop3port: account.pop3Port,
smtpserver: account.smtpServer,
smtpport: account.smtpPort,
nachrichten: account.messages.map(javaEmail),
};
}
export function javaEmail(email: JavaEmail) {
const recipient = (entry: JavaRecipient) =>
entry === null ? null : typeof entry === 'string' ? ['string', entry] : [entry.name, entry.mailAddress];
return {
absender: [email.sender.name, email.sender.mailAddress],
empfaenger: email.to.map(recipient),
cc: email.cc.map(recipient),
bcc: email.bcc.map(recipient),
betreff: email.subject,
text: email.text,
dateReceived: email.dateReceived,
neu: email.isNew,
delete: email.isDeleted,
versendet: email.isSent,
};
}
+35
View File
@@ -0,0 +1,35 @@
// How Java FILIUS finds a file by path (Dateisystem.holeDatei), as the DNS
// server and the e-mail programs use it.
import { describe, expect, it } from 'vitest';
import type { FlsFileTree } from '../src/index.js';
import { javaFileContent } from '../src/javaFileSystem.js';
const file = (name: string, content: string | null): FlsFileTree => ({ kind: 'file', name, type: 'text', content, children: [] });
const directory = (name: string, ...children: FlsFileTree[]): FlsFileTree => ({ kind: 'directory', name, children });
describe('javaFileContent', () => {
it('finds a path ignoring case, as Java does', () => {
expect(javaFileContent(directory('root', directory('DNS', file('Hosts', 'x'))), '/dns/hosts')).toBe('x');
});
it('takes the four units Java also equals with an ASCII letter (measured on the JDK)', () => {
expect(javaFileContent(directory('root', directory('dns', file('ho\u017Fts', 'long s'))), '/dns/hosts')).toBe('long s');
expect(javaFileContent(directory('root', file('\u212Aonten.txt', 'kelvin')), '/konten.txt')).toBe('kelvin');
expect(javaFileContent(directory('root', directory('ma\u0130lserver', file('konten.txt', 'dotted'))), '/mailserver/konten.txt')).toBe('dotted');
expect(javaFileContent(directory('root', directory('ma\u0131lserver', file('konten.txt', 'dotless'))), '/mailserver/konten.txt')).toBe('dotless');
expect(javaFileContent(directory('root', file('k\u00F6nten.txt', 'umlaut')), '/konten.txt')).toBeUndefined();
});
it('takes the first match in preorder, and nothing when that is a directory', () => {
const twice = directory('root', directory('dns', file('hosts', 'first')), directory('DNS', file('hosts', 'second')));
expect(javaFileContent(twice, '/dns/hosts')).toBe('first');
expect(javaFileContent(directory('root', directory('dns', directory('hosts'), file('HOSTS', 'later'))), '/dns/hosts')).toBeUndefined();
});
it('is undefined without the file, and empty for a file without content', () => {
expect(javaFileContent(undefined, '/dns/hosts')).toBeUndefined();
expect(javaFileContent(directory('root', file('hosts', 'x')), '/dns/hosts')).toBeUndefined();
expect(javaFileContent(directory('root', directory('dns', file('hosts', null))), '/dns/hosts')).toBe('');
});
});
+56
View File
@@ -18,8 +18,11 @@ import {
type FlsModem,
type FlsNodeClass,
type FlsTopology,
type JavaEmailAccount,
type JavaFailure,
} from '../src/index.js';
import { EXPECTED_WARNINGS } from './expectedWarnings.js';
import { javaEmail, javaEmailAccount } from './javaEmailNames.js';
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
const javaView = JSON.parse(readFileSync(join(corpusRoot, 'corpus.java.json'), 'utf8')) as Record<string, unknown>;
@@ -91,9 +94,39 @@ function javaApplications(applications: FlsApplication[]): unknown[] {
filterUdp: application.firewall.filterUdp,
rules: application.firewall.rules.length,
}),
...(application.mailDomain === undefined
? {}
: {
mailDomain: application.mailDomain,
beanAccounts: application.beanAccounts!.map(javaEmailAccount),
fileAccounts: javaFileAccounts(application.fileAccounts!),
}),
...(application.received === undefined
? {}
: {
received: application.received.map(javaEmail),
sent: application.sent!.map(javaEmail),
drafts: application.drafts!.map(javaEmail),
addressBook: application.addressBook!.map((contact) => [
contact.name,
contact.firstName,
contact.street,
contact.houseNumber,
contact.postalCode,
contact.city,
contact.email,
contact.phone,
]),
fileAccount: application.fileAccount === null ? null : javaEmailAccount(application.fileAccount!),
}),
}));
}
function javaFileAccounts(accounts: JavaEmailAccount[] | JavaFailure | null): unknown {
if (accounts === null) return null;
return Array.isArray(accounts) ? accounts.map(javaEmailAccount) : `!${accounts.exception}`;
}
/** Java's property names, as FlsOracle.java writes them. */
function javaDhcpServer(server: FlsDhcpServer) {
return {
@@ -423,6 +456,29 @@ describe('readTopology rules', () => {
expect(Object.keys(byClass.get('filius.software.lokal.Terminal')!).sort()).toEqual(['javaClass', 'key', 'source']);
});
it('reads the e-mail beans through their setters: null names, string senders, a checked domain', () => {
const put = (className: string, body = '') =>
`<void method="put"><string>${className}</string><object class="${className}">${body}</object></void>`;
const { nodes } = topologyOf(`<object class="java.util.LinkedList">${nodeItem(`<object class="filius.hardware.knoten.Rechner">
<void property="systemSoftware"><void property="installierteAnwendungen">
${put('filius.software.email.EmailServer', `
<void property="mailDomain"><string>1und1.de</string></void>
<void property="listeBenutzerkonten"><void method="add"><object class="filius.software.email.EmailKonto">
<void property="benutzername"><string>bob</string></void>
<void property="nachname"><null/></void>
<void property="nachrichten"><void method="add"><object class="filius.software.email.Email">
<void property="absender"><string> Alice &lt;alice@senior.de&gt; </string></void>
</object></void></void>
</object></void></void>`)}
</void></void></object>`)}</object>`);
const server = nodes[0]!.system.applications![0]!;
// setMailDomain keeps "filius.de" for a domain Java's pattern rejects.
expect(server).toMatchObject({ isActive: false, mailDomain: 'filius.de' });
expect(server.beanAccounts![0]).toMatchObject({ username: 'bob', lastName: '', firstName: '' });
expect(server.beanAccounts![0]!.messages[0]!.sender).toEqual({ name: 'Alice', mailAddress: 'alice@senior.de' });
});
it("gives a modem Java's defaults and keeps an address the file sets to null", () => {
const { nodes } = topologyOf(`<object class="java.util.LinkedList">
${nodeItem('<object class="filius.hardware.knoten.Modem"/>')}