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:
@@ -46,6 +46,9 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
|
|||||||
| `test/javabeans.test.ts` | — | neu: je XMLDecoder-Regel ein Test; alle 23 Korpus-Dateien, Klasse und Position jedes Geräts wie Java |
|
| `test/javabeans.test.ts` | — | neu: je XMLDecoder-Regel ein Test; alle 23 Korpus-Dateien, Klasse und Position jedes Geräts wie Java |
|
||||||
| `test/topology.test.ts` | — | neu: alle 23 Korpus-Dateien Feld für Feld wie Java (Geräte, Schnittstellen, Kabel, Doku-Elemente), dazu die Regeln, die der Korpus nicht berührt |
|
| `test/topology.test.ts` | — | neu: alle 23 Korpus-Dateien Feld für Feld wie Java (Geräte, Schnittstellen, Kabel, Doku-Elemente), dazu die Regeln, die der Korpus nicht berührt |
|
||||||
| `test/javaIp.test.ts`, `test/ip-check.*` | — | neu: `IP.ipCheck`-Fälle, Ergebnis aus Java |
|
| `test/javaIp.test.ts`, `test/ip-check.*` | — | neu: `IP.ipCheck`-Fälle, Ergebnis aus Java |
|
||||||
|
| `src/javaEmail.ts` | Java `EmailServer` (Konten-Bean, `kontenLaden`/`stringZuListeBenutzerkonten`), `EmailAnwendung` (Ordner, Adressbuch, `laden`), `EmailKonto`, `Email`, `AddressEntry`, `EmailUtils` | neu nach Java: E-Mail-Beans über ihre Setter, `/mailserver/konten.txt` und `/konten.txt` wie Javas Leser (StringTokenizer überspringt leere Felder, `split` verliert leere Felder am Ende); Empfänger älterer Dateien als Zeichenketten; gegen Java auf dem Korpus und an 43 Fällen geprüft |
|
||||||
|
| `src/javaFileSystem.ts`, `src/javaString.ts` | Java `Dateisystem.holeDatei`, `String.trim`/`split`/`equalsIgnoreCase`, `StringTokenizer` | neu nach Java; die Groß-/Kleinschreibung gegenüber ASCII am JDK über die ganze BMP gemessen |
|
||||||
|
| `test/javaEmail.test.ts`, `test/email-*.cases.txt`, `test/email-*.java.json`, `test/javaEmailNames.ts`, `test/javaFileSystem.test.ts` | — | neu: Kontodateien von Server und Client, Ergebnis aus Java; Dateifund |
|
||||||
| `test/dnsHosts.test.ts`, `test/dns-hosts.*` | — | neu: `/dns/hosts`-Fälle, Javas Sicht wie Java, das Modell wie Swift bis auf 33 benannte Fälle in zwölf Gründen |
|
| `test/dnsHosts.test.ts`, `test/dns-hosts.*` | — | neu: `/dns/hosts`-Fälle, Javas Sicht wie Java, das Modell wie Swift bis auf 33 benannte Fälle in zwölf Gründen |
|
||||||
| `test/zip.test.ts` | `TopologyProjectPersistenceTests.swift` (Archiv-Ablehnungstests, `makeMinimalZIP`, `makePayloadZIP`) | übertragen, ergänzt um DEFLATE-Grenzfälle |
|
| `test/zip.test.ts` | `TopologyProjectPersistenceTests.swift` (Archiv-Ablehnungstests, `makeMinimalZIP`, `makePayloadZIP`) | übertragen, ergänzt um DEFLATE-Grenzfälle |
|
||||||
| `test/corpus.test.ts` | — | neu: 23 Korpus-Dateien, gegengeprüft mit `fflate.unzipSync` |
|
| `test/corpus.test.ts` | — | neu: 23 Korpus-Dateien, gegengeprüft mit `fflate.unzipSync` |
|
||||||
|
|||||||
+1055
-23
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,9 @@
|
|||||||
// - duplicates stay.
|
// - duplicates stay.
|
||||||
|
|
||||||
import type { DnsResourceRecord } from '@mdg-filius/core';
|
import type { DnsResourceRecord } from '@mdg-filius/core';
|
||||||
|
import { javaFileContent } from './javaFileSystem.js';
|
||||||
import { javaIpCheck, javaParseInt } from './javaIp.js';
|
import { javaIpCheck, javaParseInt } from './javaIp.js';
|
||||||
|
import { javaTrim } from './javaString.js';
|
||||||
import type { FlsFileTree } from './systemSoftware.js';
|
import type { FlsFileTree } from './systemSoftware.js';
|
||||||
|
|
||||||
/** A record as Java's ResourceRecord holds it (the getters). */
|
/** 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
|
// EingabenUeberpruefung.musterDomain, including its quirks: labels start with
|
||||||
// a letter, labels after the first may hold a backslash, and the empty
|
// a letter, labels after the first may hold a backslash, and the empty
|
||||||
// string is a valid domain.
|
// 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.
|
// EingabenUeberpruefung.musterIpAdresse.
|
||||||
const JAVA_IP_ADDRESS =
|
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]))$/;
|
/^(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. */
|
/** The content of /dns/hosts as Java's DNS server finds it (see javaFileSystem.ts). */
|
||||||
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).
|
|
||||||
*/
|
|
||||||
export function javaHostsFileContent(root: FlsFileTree | undefined): string | undefined {
|
export function javaHostsFileContent(root: FlsFileTree | undefined): string | undefined {
|
||||||
if (root === undefined) return undefined;
|
return javaFileContent(root, DNS_HOSTS_PATH);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** `DNSServer.leseRecordListe` on the content of /dns/hosts. */
|
/** `DNSServer.leseRecordListe` on the content of /dns/hosts. */
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
export * from './archive.js';
|
export * from './archive.js';
|
||||||
export * from './dnsHosts.js';
|
export * from './dnsHosts.js';
|
||||||
export * from './importProject.js';
|
export * from './importProject.js';
|
||||||
|
export * from './javaEmail.js';
|
||||||
|
export * from './javaFileSystem.js';
|
||||||
export * from './javaIp.js';
|
export * from './javaIp.js';
|
||||||
|
export * from './javaString.js';
|
||||||
export * from './javabeans.js';
|
export * from './javabeans.js';
|
||||||
export * from './systemSoftware.js';
|
export * from './systemSoftware.js';
|
||||||
export * from './topology.js';
|
export * from './topology.js';
|
||||||
|
|||||||
@@ -0,0 +1,304 @@
|
|||||||
|
// Java's e-mail programs as FILIUS 2.14 loads them. Two places hold their
|
||||||
|
// state:
|
||||||
|
// - the file's JavaBeans: the server's accounts (listeBenutzerkonten, with
|
||||||
|
// their mailboxes), the client's three folders and address book;
|
||||||
|
// - text files in the host's file system: /mailserver/konten.txt, which the
|
||||||
|
// server reads (kontenLaden) whenever a simulation starts and so wins over
|
||||||
|
// the bean list, and /konten.txt, the client's account, read (laden) when
|
||||||
|
// its window opens. The client's account is in no bean: its accessors
|
||||||
|
// (holeKontoListe, setzeKontoListe) are not bean properties.
|
||||||
|
// Checked against Java on the corpus (corpus.java.json, applications).
|
||||||
|
|
||||||
|
import { isJavaObject, listItems, simpleClassName, type JavaObject, type JavaValue } from './javabeans.js';
|
||||||
|
import { javaSplit, javaTokens, javaTrim } from './javaString.js';
|
||||||
|
|
||||||
|
/** AddressEntry: a display name, an address, or both. */
|
||||||
|
export interface JavaAddressEntry {
|
||||||
|
readonly name: string | null;
|
||||||
|
readonly mailAddress: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A recipient as the list holds it: older files keep recipients as plain
|
||||||
|
* strings, which the decoder puts into the typed list unchecked.
|
||||||
|
*/
|
||||||
|
export type JavaRecipient = JavaAddressEntry | string | null;
|
||||||
|
|
||||||
|
export interface JavaEmail {
|
||||||
|
readonly sender: JavaAddressEntry;
|
||||||
|
readonly to: readonly JavaRecipient[];
|
||||||
|
readonly cc: readonly JavaRecipient[];
|
||||||
|
readonly bcc: readonly JavaRecipient[];
|
||||||
|
readonly subject: string | null;
|
||||||
|
readonly text: string | null;
|
||||||
|
readonly dateReceived: string | null;
|
||||||
|
readonly isNew: boolean;
|
||||||
|
readonly isDeleted: boolean;
|
||||||
|
readonly isSent: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** EmailKonto: on the server an account with its mailbox, in the client the account it uses. */
|
||||||
|
export interface JavaEmailAccount {
|
||||||
|
readonly username: string | null;
|
||||||
|
readonly password: string | null;
|
||||||
|
readonly lastName: string;
|
||||||
|
readonly firstName: string;
|
||||||
|
readonly emailAddress: string | null;
|
||||||
|
readonly pop3Server: string | null;
|
||||||
|
readonly pop3Port: string | null;
|
||||||
|
readonly smtpServer: string | null;
|
||||||
|
readonly smtpPort: string | null;
|
||||||
|
readonly messages: readonly JavaEmail[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Kontakt: an address book entry. */
|
||||||
|
export interface JavaContact {
|
||||||
|
readonly name: string | null;
|
||||||
|
readonly firstName: string | null;
|
||||||
|
readonly street: string | null;
|
||||||
|
readonly houseNumber: number;
|
||||||
|
readonly postalCode: number;
|
||||||
|
readonly city: string | null;
|
||||||
|
readonly email: string | null;
|
||||||
|
readonly phone: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An exception Java throws while reading, by its simple class name. */
|
||||||
|
export interface JavaFailure {
|
||||||
|
readonly exception: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EMAIL_SERVER_FILE = '/mailserver/konten.txt';
|
||||||
|
export const EMAIL_CLIENT_FILE = '/konten.txt';
|
||||||
|
|
||||||
|
type Warn = (message: string) => void;
|
||||||
|
|
||||||
|
// JavaBeans
|
||||||
|
|
||||||
|
export function javaEmailAccounts(list: JavaValue | undefined, warn: Warn): JavaEmailAccount[] {
|
||||||
|
return objectsOf(list, 'EmailKonto', warn).map((account) => {
|
||||||
|
const at = (property: string) => text(account.properties.get(property), `EmailKonto ${property}`, warn);
|
||||||
|
return {
|
||||||
|
username: at('benutzername'),
|
||||||
|
password: at('passwort'),
|
||||||
|
// setNachname and setVorname keep "" for null.
|
||||||
|
lastName: at('nachname') ?? '',
|
||||||
|
firstName: at('vorname') ?? '',
|
||||||
|
emailAddress: at('emailAdresse'),
|
||||||
|
pop3Server: at('pop3server'),
|
||||||
|
pop3Port: at('pop3port'),
|
||||||
|
smtpServer: at('smtpserver'),
|
||||||
|
smtpPort: at('smtpport'),
|
||||||
|
messages: javaEmails(account.properties.get('nachrichten'), warn),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function javaEmails(list: JavaValue | undefined, warn: Warn): JavaEmail[] {
|
||||||
|
return objectsOf(list, 'Email', warn).map((email) => {
|
||||||
|
const at = (property: string) => email.properties.get(property);
|
||||||
|
const flag = (property: string, fallback: boolean) => {
|
||||||
|
const value = at(property);
|
||||||
|
if (value === undefined) return fallback;
|
||||||
|
if (typeof value === 'boolean') return value;
|
||||||
|
warn(`Email ${property} is not a boolean, used ${fallback}`);
|
||||||
|
return fallback;
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
sender: senderOf(at('absender'), warn),
|
||||||
|
to: recipientsOf(at('empfaenger'), warn),
|
||||||
|
cc: recipientsOf(at('cc'), warn),
|
||||||
|
bcc: recipientsOf(at('bcc'), warn),
|
||||||
|
subject: text(at('betreff'), 'Email betreff', warn),
|
||||||
|
text: at('text') === undefined ? '' : text(at('text'), 'Email text', warn),
|
||||||
|
dateReceived: at('dateReceived') === undefined ? '' : text(at('dateReceived'), 'Email dateReceived', warn),
|
||||||
|
isNew: flag('neu', true),
|
||||||
|
isDeleted: flag('delete', false),
|
||||||
|
isSent: flag('versendet', false),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function javaContacts(list: JavaValue | undefined, warn: Warn): JavaContact[] {
|
||||||
|
return objectsOf(list, 'Kontakt', warn).map((contact) => {
|
||||||
|
const at = (property: string) => text(contact.properties.get(property), `Kontakt ${property}`, warn);
|
||||||
|
const number = (property: string) => {
|
||||||
|
const value = contact.properties.get(property);
|
||||||
|
if (value === undefined) return 0;
|
||||||
|
if (typeof value === 'number' && Number.isInteger(value)) return value;
|
||||||
|
warn(`Kontakt ${property} is not an int, used 0`);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
name: at('name'),
|
||||||
|
firstName: at('vorname'),
|
||||||
|
street: at('strasse'),
|
||||||
|
houseNumber: number('hausnr'),
|
||||||
|
postalCode: number('plz'),
|
||||||
|
city: at('wohnort'),
|
||||||
|
email: at('email'),
|
||||||
|
phone: at('telefon'),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Email.getAbsender(): a copy, so never null — a missing sender has neither name nor address. */
|
||||||
|
function senderOf(value: JavaValue | undefined, warn: Warn): JavaAddressEntry {
|
||||||
|
if (typeof value === 'string') return addressEntry(value);
|
||||||
|
if (isAddressEntry(value)) return beanAddressEntry(value, warn);
|
||||||
|
if (value !== undefined && value !== null) warn('Email absender is neither a string nor an AddressEntry, ignored');
|
||||||
|
return { name: null, mailAddress: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
function recipientsOf(list: JavaValue | undefined, warn: Warn): JavaRecipient[] {
|
||||||
|
return listItems(list).flatMap((item): JavaRecipient[] => {
|
||||||
|
if (item === null || typeof item === 'string') return [item];
|
||||||
|
if (isAddressEntry(item)) return [beanAddressEntry(item, warn)];
|
||||||
|
warn('an e-mail recipient is neither a string nor an AddressEntry, left out');
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An AddressEntry — also the copy a getter returns, which the file fills and
|
||||||
|
* then sets (`<void id="AddressEntry0" property="absender">`): the decoder
|
||||||
|
* names such an object by its id.
|
||||||
|
*/
|
||||||
|
function isAddressEntry(value: JavaValue | undefined): value is JavaObject {
|
||||||
|
return isJavaObject(value) && simpleClassName(value.className) === 'AddressEntry';
|
||||||
|
}
|
||||||
|
|
||||||
|
function beanAddressEntry(entry: JavaObject, warn: Warn): JavaAddressEntry {
|
||||||
|
return {
|
||||||
|
name: text(entry.properties.get('name'), 'AddressEntry name', warn),
|
||||||
|
mailAddress: text(entry.properties.get('mailAddress'), 'AddressEntry mailAddress', warn),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function objectsOf(list: JavaValue | undefined, simpleClass: string, warn: Warn): JavaObject[] {
|
||||||
|
return listItems(list).flatMap((item) => {
|
||||||
|
if (isJavaObject(item) && simpleClassName(item.className) === simpleClass) return [item];
|
||||||
|
warn(`a list of ${simpleClass} holds something else, left out`);
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function text(value: JavaValue | undefined, name: string, warn: Warn): string | null {
|
||||||
|
if (value === undefined || value === null) return null;
|
||||||
|
if (typeof value === 'string') return value;
|
||||||
|
warn(`${name} is not a string, ignored`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text files
|
||||||
|
|
||||||
|
/** `new AddressEntry(text)`: "Name <address>", "<address>", a bare address with "@", else a name. */
|
||||||
|
export function addressEntry(raw: string): JavaAddressEntry {
|
||||||
|
const entry = javaTrim(raw);
|
||||||
|
const start = entry.indexOf('<');
|
||||||
|
const end = entry.indexOf('>');
|
||||||
|
if (start >= 0 && end > start) {
|
||||||
|
return { name: start > 0 ? javaTrim(entry.slice(0, start)) : null, mailAddress: javaTrim(entry.slice(start + 1, end)) };
|
||||||
|
}
|
||||||
|
if (!entry.includes('<') && !entry.includes('>') && entry.includes('@')) return { name: null, mailAddress: entry };
|
||||||
|
return { name: entry, mailAddress: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** `EmailUtils.stringToAddressEntryList`: comma-separated, blank parts skipped. */
|
||||||
|
function addressEntries(raw: string): JavaAddressEntry[] {
|
||||||
|
return javaSplit(raw, ',')
|
||||||
|
.filter((part) => javaTrim(part) !== '')
|
||||||
|
.map(addressEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `EmailServer.stringZuListeBenutzerkonten`: one account a line —
|
||||||
|
* user;domain;password;last name;first name;messages — split by a
|
||||||
|
* StringTokenizer, which skips empty fields; an account with fewer than five
|
||||||
|
* fields makes Java throw. Messages are "#"-separated, seven "$"-separated
|
||||||
|
* fields each: sender, to, cc, bcc, date, subject, text. Java's split drops
|
||||||
|
* trailing empty fields, so a message with an empty text is lost.
|
||||||
|
*/
|
||||||
|
export function readJavaEmailServerFile(content: string): JavaEmailAccount[] | JavaFailure {
|
||||||
|
const accounts: JavaEmailAccount[] = [];
|
||||||
|
for (const line of javaTokens(content, '\n')) {
|
||||||
|
const fields = javaTokens(line, ';');
|
||||||
|
if (fields.length < 5) return { exception: 'NoSuchElementException' };
|
||||||
|
const [username, , password, lastName, firstName, messages] = fields as [string, string, string, string, string, string?];
|
||||||
|
accounts.push({
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
lastName,
|
||||||
|
firstName,
|
||||||
|
emailAddress: null,
|
||||||
|
pop3Server: null,
|
||||||
|
pop3Port: null,
|
||||||
|
smtpServer: null,
|
||||||
|
smtpPort: null,
|
||||||
|
messages: messages === undefined ? [] : fileMessages(messages),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileMessages(messages: string): JavaEmail[] {
|
||||||
|
return javaSplit(messages, '#').flatMap((message) => {
|
||||||
|
const fields = javaSplit(message, '$');
|
||||||
|
if (fields.length !== 7) return [];
|
||||||
|
const [sender, to, cc, bcc, dateReceived, subject, body] = fields as [string, string, string, string, string, string, string];
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
sender: addressEntry(sender),
|
||||||
|
to: addressEntries(to),
|
||||||
|
cc: addressEntries(cc),
|
||||||
|
bcc: addressEntries(bcc),
|
||||||
|
subject,
|
||||||
|
text: unescapedBody(body),
|
||||||
|
dateReceived,
|
||||||
|
isNew: true,
|
||||||
|
isDeleted: false,
|
||||||
|
isSent: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** brtonl, then undoSpecialChar: "|" is a line break, "&&00124&&" a "|". */
|
||||||
|
function unescapedBody(body: string): string {
|
||||||
|
return body
|
||||||
|
.replaceAll('|', '\n')
|
||||||
|
.replaceAll('&&00036&&', '$')
|
||||||
|
.replaceAll('&&00059&&', ';')
|
||||||
|
.replaceAll('&&00124&&', '|')
|
||||||
|
.replaceAll('&&00035&&', '#');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `EmailAnwendung.laden`: every line makes an account, the last one stays.
|
||||||
|
* `new EmailKonto(line)` takes nine ";"-separated fields — POP3 server, SMTP
|
||||||
|
* server, POP3 port, SMTP port, user, password, last name, first name,
|
||||||
|
* address — and with fewer keeps none (trailing empty fields do not count).
|
||||||
|
* Null when the file has no line at all.
|
||||||
|
*/
|
||||||
|
export function readJavaEmailClientFile(content: string): JavaEmailAccount | null {
|
||||||
|
const lines = javaSplit(content, '\n');
|
||||||
|
const line = lines[lines.length - 1];
|
||||||
|
if (line === undefined) return null;
|
||||||
|
const fields = javaSplit(line, ';');
|
||||||
|
const empty = { username: null, password: null, lastName: '', firstName: '', emailAddress: null };
|
||||||
|
const servers = { pop3Server: null, pop3Port: null, smtpServer: null, smtpPort: null };
|
||||||
|
if (fields.length <= 8) return { ...empty, ...servers, messages: [] };
|
||||||
|
const [pop3Server, smtpServer, pop3Port, smtpPort, username, password, lastName, firstName, emailAddress] = fields as string[];
|
||||||
|
return {
|
||||||
|
username: username!,
|
||||||
|
password: password!,
|
||||||
|
lastName: lastName!,
|
||||||
|
firstName: firstName!,
|
||||||
|
emailAddress: emailAddress!,
|
||||||
|
pop3Server: pop3Server!,
|
||||||
|
pop3Port: pop3Port!,
|
||||||
|
smtpServer: smtpServer!,
|
||||||
|
smtpPort: smtpPort!,
|
||||||
|
messages: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// How Java FILIUS finds a file by path (Dateisystem.holeDatei): the first
|
||||||
|
// node in preorder whose path equals the path ignoring case, and only if that
|
||||||
|
// node is a file.
|
||||||
|
|
||||||
|
import type { FlsFileTree } from './systemSoftware.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The content of the file at an ASCII path as `Dateisystem.holeDatei` finds
|
||||||
|
* it. Undefined when there is none; a file without content reads as empty
|
||||||
|
* (Java would fail on it).
|
||||||
|
*/
|
||||||
|
export function javaFileContent(root: FlsFileTree | undefined, asciiPath: string): string | undefined {
|
||||||
|
if (root === undefined) return undefined;
|
||||||
|
const found = firstInPreorder(root, '', (path) => javaEqualsIgnoreCase(path, asciiPath));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Besides ASCII case, String.equalsIgnoreCase takes these BMP units for an
|
||||||
|
// ASCII letter (measured on the JDK over every unit): dotted and dotless I,
|
||||||
|
// the Kelvin sign and the long s.
|
||||||
|
const JAVA_ASCII_CASE_FOLDS = new Map([
|
||||||
|
['\u0130', 'i'],
|
||||||
|
['\u0131', 'i'],
|
||||||
|
['\u212A', 'k'],
|
||||||
|
['\u017F', 's'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** `String.equalsIgnoreCase` against an ASCII path, unit by unit. */
|
||||||
|
function javaEqualsIgnoreCase(text: string, asciiPath: string): boolean {
|
||||||
|
if (text.length !== asciiPath.length) return false;
|
||||||
|
for (let index = 0; index < text.length; index++) {
|
||||||
|
if (asciiFold(text[index]!) !== asciiFold(asciiPath[index]!)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function asciiFold(unit: string): string {
|
||||||
|
if (unit >= 'A' && unit <= 'Z') return unit.toLowerCase();
|
||||||
|
return JAVA_ASCII_CASE_FOLDS.get(unit) ?? unit;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// java.lang.String and java.util.StringTokenizer as FILIUS's text formats
|
||||||
|
// use them — each differs from its JavaScript namesake.
|
||||||
|
|
||||||
|
/** `String.trim()`: every UTF-16 unit up to U+0020 off both ends. */
|
||||||
|
export function javaTrim(text: string): string {
|
||||||
|
return text.replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `text.split(separator)` for a one-character separator: trailing empty
|
||||||
|
* parts are dropped — "a;b;;".split(";") is two parts, ";;" none — but a
|
||||||
|
* text without the separator is one part, even the empty text.
|
||||||
|
*/
|
||||||
|
export function javaSplit(text: string, separator: string): string[] {
|
||||||
|
if (!text.includes(separator)) return [text];
|
||||||
|
const parts = text.split(separator);
|
||||||
|
while (parts.length > 0 && parts[parts.length - 1] === '') parts.pop();
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** `new StringTokenizer(text, separator)`: the non-empty runs between separators. */
|
||||||
|
export function javaTokens(text: string, separator: string): string[] {
|
||||||
|
return text.split(separator).filter((token) => token !== '');
|
||||||
|
}
|
||||||
@@ -27,6 +27,21 @@
|
|||||||
// not know Java's constructors and sees a placeholder per path; they are
|
// not know Java's constructors and sees a placeholder per path; they are
|
||||||
// merged here.
|
// merged here.
|
||||||
|
|
||||||
|
import { JAVA_DOMAIN } from './dnsHosts.js';
|
||||||
|
import {
|
||||||
|
EMAIL_CLIENT_FILE,
|
||||||
|
EMAIL_SERVER_FILE,
|
||||||
|
javaContacts,
|
||||||
|
javaEmailAccounts,
|
||||||
|
javaEmails,
|
||||||
|
readJavaEmailClientFile,
|
||||||
|
readJavaEmailServerFile,
|
||||||
|
type JavaContact,
|
||||||
|
type JavaEmail,
|
||||||
|
type JavaEmailAccount,
|
||||||
|
type JavaFailure,
|
||||||
|
} from './javaEmail.js';
|
||||||
|
import { javaFileContent } from './javaFileSystem.js';
|
||||||
import {
|
import {
|
||||||
isJavaObject,
|
isJavaObject,
|
||||||
isUnresolved,
|
isUnresolved,
|
||||||
@@ -114,12 +129,12 @@ const SERVER_APPLICATIONS = new Set<string>([
|
|||||||
'filius.software.www.WebServer',
|
'filius.software.www.WebServer',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/** An installed application with the settings the model reads; e-mail contents are a later layer. */
|
/** An installed application with the settings the model reads. */
|
||||||
export interface FlsApplication {
|
export interface FlsApplication {
|
||||||
/** The key in installierteAnwendungen — the class name in every corpus file. */
|
/** The key in installierteAnwendungen — the class name in every corpus file. */
|
||||||
key: string | null;
|
key: string | null;
|
||||||
javaClass: FlsApplicationClass;
|
javaClass: FlsApplicationClass;
|
||||||
/** Server applications: switched on (Java's aktiv) and the port. */
|
/** Server applications: switched on (Java's aktiv) and the port; EmailServer: switched on. */
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
port?: number;
|
port?: number;
|
||||||
/** ClientBaustein: the server it talks to (Java's zielIPAdresse, zielPort). */
|
/** ClientBaustein: the server it talks to (Java's zielIPAdresse, zielPort). */
|
||||||
@@ -136,6 +151,23 @@ export interface FlsApplication {
|
|||||||
maxPeers?: number | null;
|
maxPeers?: number | null;
|
||||||
/** Firewall: the personal firewall. */
|
/** Firewall: the personal firewall. */
|
||||||
firewall?: FlsFirewall;
|
firewall?: FlsFirewall;
|
||||||
|
/** EmailServer: its domain (Java keeps "filius.de" for one its pattern rejects). */
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
fileAccounts?: JavaEmailAccount[] | JavaFailure | null;
|
||||||
|
/** EmailAnwendung: its folders (Java's empfangene, gesendete, erstellte Nachrichten). */
|
||||||
|
received?: JavaEmail[];
|
||||||
|
sent?: JavaEmail[];
|
||||||
|
drafts?: JavaEmail[];
|
||||||
|
addressBook?: JavaContact[];
|
||||||
|
/** EmailAnwendung: the account of /konten.txt, null without one. */
|
||||||
|
fileAccount?: JavaEmailAccount | null;
|
||||||
/** The application's JavaBean, for the e-mail layer. */
|
/** The application's JavaBean, for the e-mail layer. */
|
||||||
source: JavaObject;
|
source: JavaObject;
|
||||||
}
|
}
|
||||||
@@ -188,7 +220,7 @@ export function readSystemSoftware(device: JavaObject, javaClass: FlsNodeClass,
|
|||||||
case 'Notebook':
|
case 'Notebook':
|
||||||
result.ssid = optionalString(value('ssid'), 'ssid', warn) ?? null;
|
result.ssid = optionalString(value('ssid'), 'ssid', warn) ?? null;
|
||||||
result.fileSystem = fileSystem(value('dateisystem'), warn);
|
result.fileSystem = fileSystem(value('dateisystem'), warn);
|
||||||
result.applications = applications(value('installierteAnwendungen'), warn);
|
result.applications = applications(value('installierteAnwendungen'), result.fileSystem, warn);
|
||||||
break;
|
break;
|
||||||
case 'Vermittlungsrechner':
|
case 'Vermittlungsrechner':
|
||||||
result.ripEnabled = boolean(value('ripEnabled'), false, 'ripEnabled', warn);
|
result.ripEnabled = boolean(value('ripEnabled'), false, 'ripEnabled', warn);
|
||||||
@@ -234,7 +266,7 @@ function modemAddress(value: string | null | undefined): string | null {
|
|||||||
* a project's own application, compiled into projekt/anwendungen — is left
|
* a project's own application, compiled into projekt/anwendungen — is left
|
||||||
* out: this reader cannot run Java classes.
|
* out: this reader cannot run Java classes.
|
||||||
*/
|
*/
|
||||||
function applications(map: JavaValue | undefined, warn: Warn): FlsApplication[] {
|
function applications(map: JavaValue | undefined, root: FlsFileTree, warn: Warn): FlsApplication[] {
|
||||||
const result: FlsApplication[] = [];
|
const result: FlsApplication[] = [];
|
||||||
for (const [key, application] of mapEntries(map)) {
|
for (const [key, application] of mapEntries(map)) {
|
||||||
if (!isJavaObject(application)) {
|
if (!isJavaObject(application)) {
|
||||||
@@ -278,6 +310,24 @@ function applications(map: JavaValue | undefined, warn: Warn): FlsApplication[]
|
|||||||
case 'filius.software.firewall.Firewall':
|
case 'filius.software.firewall.Firewall':
|
||||||
entry.firewall = firewallOf([application], true, warn);
|
entry.firewall = firewallOf([application], true, warn);
|
||||||
break;
|
break;
|
||||||
|
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.beanAccounts = javaEmailAccounts(at('listeBenutzerkonten'), warn);
|
||||||
|
const file = javaFileContent(root, EMAIL_SERVER_FILE);
|
||||||
|
entry.fileAccounts = file === undefined ? null : readJavaEmailServerFile(file);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'filius.software.email.EmailAnwendung': {
|
||||||
|
entry.received = javaEmails(at('empfangeneNachrichten'), warn);
|
||||||
|
entry.sent = javaEmails(at('gesendeteNachrichten'), 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);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result.push(entry);
|
result.push(entry);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import { readFileSync } from 'node:fs';
|
|||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import type { FlsFileTree } from '../src/index.js';
|
import { dnsRecordsOf, readJavaDnsHosts } from '../src/dnsHosts.js';
|
||||||
import { dnsRecordsOf, javaHostsFileContent, readJavaDnsHosts } from '../src/dnsHosts.js';
|
|
||||||
|
|
||||||
const here = dirname(fileURLToPath(import.meta.url));
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
type Row = [string, string, number, string];
|
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)"]);
|
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('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
@@ -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 });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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('');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -18,8 +18,11 @@ import {
|
|||||||
type FlsModem,
|
type FlsModem,
|
||||||
type FlsNodeClass,
|
type FlsNodeClass,
|
||||||
type FlsTopology,
|
type FlsTopology,
|
||||||
|
type JavaEmailAccount,
|
||||||
|
type JavaFailure,
|
||||||
} from '../src/index.js';
|
} from '../src/index.js';
|
||||||
import { EXPECTED_WARNINGS } from './expectedWarnings.js';
|
import { EXPECTED_WARNINGS } from './expectedWarnings.js';
|
||||||
|
import { javaEmail, javaEmailAccount } from './javaEmailNames.js';
|
||||||
|
|
||||||
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
|
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
|
||||||
const javaView = JSON.parse(readFileSync(join(corpusRoot, 'corpus.java.json'), 'utf8')) as Record<string, unknown>;
|
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,
|
filterUdp: application.firewall.filterUdp,
|
||||||
rules: application.firewall.rules.length,
|
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. */
|
/** Java's property names, as FlsOracle.java writes them. */
|
||||||
function javaDhcpServer(server: FlsDhcpServer) {
|
function javaDhcpServer(server: FlsDhcpServer) {
|
||||||
return {
|
return {
|
||||||
@@ -423,6 +456,29 @@ describe('readTopology rules', () => {
|
|||||||
expect(Object.keys(byClass.get('filius.software.lokal.Terminal')!).sort()).toEqual(['javaClass', 'key', 'source']);
|
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 <alice@senior.de> </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", () => {
|
it("gives a modem Java's defaults and keeps an address the file sets to null", () => {
|
||||||
const { nodes } = topologyOf(`<object class="java.util.LinkedList">
|
const { nodes } = topologyOf(`<object class="java.util.LinkedList">
|
||||||
${nodeItem('<object class="filius.hardware.knoten.Modem"/>')}
|
${nodeItem('<object class="filius.hardware.knoten.Modem"/>')}
|
||||||
|
|||||||
@@ -32,3 +32,27 @@ packages/fls/::adresse
|
|||||||
packages/fls/::ziel
|
packages/fls/::ziel
|
||||||
packages/fls/::teilnehmer
|
packages/fls/::teilnehmer
|
||||||
packages/fls/::zahl
|
packages/fls/::zahl
|
||||||
|
# Property and method names of Java FILIUS's e-mail classes (EmailKonto:
|
||||||
|
# benutzername, passwort, nachname, vorname, smtpserver, smtpport, nachrichten;
|
||||||
|
# Email: absender, empfaenger, betreff, neu, versendet; EmailAnwendung:
|
||||||
|
# empfangene/gesendete/erstellte Nachrichten; EmailServer.kontenLaden), as the
|
||||||
|
# Java oracle writes them; our types call them username, password, lastName,
|
||||||
|
# firstName, smtpServer, smtpPort, messages, sender, to, subject, isNew,
|
||||||
|
# isSent, received, sent and drafts.
|
||||||
|
packages/fls/::benutzername
|
||||||
|
packages/fls/::passwort
|
||||||
|
packages/fls/::nachname
|
||||||
|
packages/fls/::vorname
|
||||||
|
packages/fls/::smtpserver
|
||||||
|
packages/fls/::smtpport
|
||||||
|
packages/fls/::nachrichten
|
||||||
|
packages/fls/::absender
|
||||||
|
packages/fls/::empfaenger
|
||||||
|
packages/fls/::betreff
|
||||||
|
packages/fls/::neu
|
||||||
|
packages/fls/::versendet
|
||||||
|
packages/fls/::empfangene
|
||||||
|
packages/fls/::gesendete
|
||||||
|
packages/fls/::erstellte
|
||||||
|
packages/fls/::konten
|
||||||
|
packages/fls/::laden
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ virtuellen Bildschirm statt „headless".
|
|||||||
eigenen Vorlagen, sie stehen nicht in der Datei) und die installierten
|
eigenen Vorlagen, sie stehen nicht in der Datei) und die installierten
|
||||||
Programme eines Rechners mit ihren Einstellungen (Klassen, die Filius
|
Programme eines Rechners mit ihren Einstellungen (Klassen, die Filius
|
||||||
nicht mitbringt — eigene Anwendungen im Archiv unter `projekt/anwendungen`
|
nicht mitbringt — eigene Anwendungen im Archiv unter `projekt/anwendungen`
|
||||||
— fehlen, weil das Werkzeug nur `konfiguration.xml` lädt); Kabel (Enden als Gerät/Schnittstelle),
|
— fehlen, weil das Werkzeug nur `konfiguration.xml` lädt); E-Mail: Konten des Servers aus der Bean-Liste und aus `/mailserver/konten.txt` (`kontenLaden`), Ordner und Adressbuch des Clients und sein Konto aus `/konten.txt` (`laden`); Kabel (Enden als Gerät/Schnittstelle),
|
||||||
Doku-Elemente.
|
Doku-Elemente.
|
||||||
Systemsoftware, Dateisysteme und Anwendungen folgen schrittweise.
|
Systemsoftware, Dateisysteme und Anwendungen folgen schrittweise.
|
||||||
|
|
||||||
@@ -77,3 +77,16 @@ Zeilen nur LF; ein Name, den Javas Muster ablehnt (`1und1.de`, `müller.de`),
|
|||||||
wird leer, der Eintrag bleibt trotzdem; Typen außer A/MX/NS (auch `a`,
|
wird leer, der Eintrag bleibt trotzdem; Typen außer A/MX/NS (auch `a`,
|
||||||
`CNAME`, eine Kommentarzeile) gelten als gültig; eine TTL, die kein `int`
|
`CNAME`, eine Kommentarzeile) gelten als gültig; eine TTL, die kein `int`
|
||||||
ist, wird still 3600.
|
ist, wird still 3600.
|
||||||
|
|
||||||
|
## E-Mail-Kontodateien
|
||||||
|
|
||||||
|
`EmailFileCases server|client` legt für jede Zeile von
|
||||||
|
`packages/fls/test/email-server.cases.txt` bzw. `email-client.cases.txt`
|
||||||
|
einen Rechner mit dieser Datei an (`/mailserver/konten.txt` bzw.
|
||||||
|
`/konten.txt`) und schreibt, welche Konten `EmailServer.kontenLaden()` bzw.
|
||||||
|
`EmailAnwendung.laden()` daraus machen. Gemessen: Eine einzige Serverzeile
|
||||||
|
mit weniger als fünf Feldern lässt Java die ganze Datei verwerfen
|
||||||
|
(`NoSuchElementException` — die Simulation startet nicht); leere Felder
|
||||||
|
überspringt Java, statt sie zu zählen; eine Nachricht mit leerem Text geht
|
||||||
|
verloren, weil `split` leere Felder am Ende abschneidet. Beim Client gewinnt
|
||||||
|
die letzte Zeile, auch eine kaputte, die das Konto leert.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ if [ ! -f "$upstream/target/filius.jar" ]; then
|
|||||||
fi
|
fi
|
||||||
classpath="$upstream/target/filius.jar:$upstream/target/lib/*"
|
classpath="$upstream/target/filius.jar:$upstream/target/lib/*"
|
||||||
mkdir -p "$here/.build"
|
mkdir -p "$here/.build"
|
||||||
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java"
|
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java"
|
||||||
# FILIUS writes its settings below $HOME; keep them out of the real one.
|
# FILIUS writes its settings below $HOME; keep them out of the real one.
|
||||||
mkdir -p "$here/.build/home"
|
mkdir -p "$here/.build/home"
|
||||||
# Removed first: a crashed run must not leave the previous result standing.
|
# Removed first: a crashed run must not leave the previous result standing.
|
||||||
@@ -73,3 +73,12 @@ dnshosts="$root/packages/fls/test/dns-hosts.java.json"
|
|||||||
rm -f "$dnshosts"
|
rm -f "$dnshosts"
|
||||||
HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" DnsHostsCases "$cases" "$dnshosts" 2>&1 | grep -v 'logback\|^$' || true
|
HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" DnsHostsCases "$cases" "$dnshosts" 2>&1 | grep -v 'logback\|^$' || true
|
||||||
test -s "$dnshosts"
|
test -s "$dnshosts"
|
||||||
|
|
||||||
|
# EmailServer.kontenLaden and EmailAnwendung.laden on hand-picked account files (packages/fls/src/javaEmail.ts).
|
||||||
|
for kind in server client; do
|
||||||
|
cases="$root/packages/fls/test/email-$kind.cases.txt"
|
||||||
|
output="$root/packages/fls/test/email-$kind.java.json"
|
||||||
|
rm -f "$output"
|
||||||
|
HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" EmailFileCases "$kind" "$cases" "$output" 2>&1 | grep -v 'logback\|^$' || true
|
||||||
|
test -s "$output"
|
||||||
|
done
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// Runs FILIUS's own readers of the e-mail account files on every case of a
|
||||||
|
// list: EmailServer.kontenLaden on /mailserver/konten.txt ("server") and
|
||||||
|
// EmailAnwendung.laden on /konten.txt ("client"). Writes the accounts Java
|
||||||
|
// ends up with — the reference for packages/fls/src/javaEmail.ts. One case
|
||||||
|
// per line: a whole file, ASCII with Java-style unicode escapes (backslash,
|
||||||
|
// u, four hex digits).
|
||||||
|
//
|
||||||
|
// Usage: java -cp filius.jar:lib/*:classes EmailFileCases server|client <cases txt> <output json>
|
||||||
|
|
||||||
|
import filius.hardware.knoten.Rechner;
|
||||||
|
import filius.software.email.EmailAnwendung;
|
||||||
|
import filius.software.email.EmailKonto;
|
||||||
|
import filius.software.email.EmailServer;
|
||||||
|
import filius.software.system.Datei;
|
||||||
|
import filius.software.system.Dateisystem;
|
||||||
|
import filius.software.system.InternetKnotenBetriebssystem;
|
||||||
|
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class EmailFileCases {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
boolean server = args[0].equals("server");
|
||||||
|
List<Object> result = new ArrayList<>();
|
||||||
|
for (String line : Files.readAllLines(Path.of(args[1]), StandardCharsets.US_ASCII)) {
|
||||||
|
InternetKnotenBetriebssystem system = (InternetKnotenBetriebssystem) new Rechner().getSystemSoftware();
|
||||||
|
Dateisystem fileSystem = system.getDateisystem();
|
||||||
|
Datei file = new Datei();
|
||||||
|
file.setName("konten.txt");
|
||||||
|
file.setDateiInhalt(IpCheckCases.unescape(line));
|
||||||
|
Object accounts;
|
||||||
|
try {
|
||||||
|
if (server) {
|
||||||
|
EmailServer mail = new EmailServer();
|
||||||
|
mail.setSystemSoftware(system);
|
||||||
|
fileSystem.speicherDatei(fileSystem.holeRootPfad() + Dateisystem.FILE_SEPARATOR + "mailserver", file);
|
||||||
|
mail.kontenLaden();
|
||||||
|
accounts = FlsOracle.emailAccounts(mail.getListeBenutzerkonten());
|
||||||
|
} else {
|
||||||
|
EmailAnwendung mail = new EmailAnwendung();
|
||||||
|
mail.setSystemSoftware(system);
|
||||||
|
fileSystem.speicherDatei(fileSystem.getRoot(), file);
|
||||||
|
mail.laden();
|
||||||
|
Map<String, EmailKonto> konten = mail.holeKontoListe();
|
||||||
|
accounts = konten.isEmpty() ? null : FlsOracle.emailAccounts(new ArrayList<>(konten.values())).get(0);
|
||||||
|
}
|
||||||
|
} catch (RuntimeException error) {
|
||||||
|
accounts = "!" + error.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
result.add(Arrays.asList(line, accounts));
|
||||||
|
}
|
||||||
|
try (FileOutputStream out = new FileOutputStream(args[2])) {
|
||||||
|
out.write((FlsOracle.Json.write(result, "") + "\n").getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
System.out.println("email " + args[0] + " cases: " + result.size());
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,12 @@ import filius.software.clientserver.ServerAnwendung;
|
|||||||
import filius.software.dateiaustausch.PeerToPeerAnwendung;
|
import filius.software.dateiaustausch.PeerToPeerAnwendung;
|
||||||
import filius.software.dhcp.DHCPServer;
|
import filius.software.dhcp.DHCPServer;
|
||||||
import filius.software.dns.DNSServer;
|
import filius.software.dns.DNSServer;
|
||||||
|
import filius.software.email.AddressEntry;
|
||||||
|
import filius.software.email.Email;
|
||||||
|
import filius.software.email.EmailAnwendung;
|
||||||
|
import filius.software.email.EmailKonto;
|
||||||
|
import filius.software.email.EmailServer;
|
||||||
|
import filius.software.email.Kontakt;
|
||||||
import filius.software.www.WebServer;
|
import filius.software.www.WebServer;
|
||||||
import filius.software.firewall.Firewall;
|
import filius.software.firewall.Firewall;
|
||||||
import filius.software.firewall.FirewallRule;
|
import filius.software.firewall.FirewallRule;
|
||||||
@@ -296,8 +302,11 @@ public class FlsOracle {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The installed applications of a PC or notebook, by key: the class, and
|
* The installed applications of a PC or notebook, by key: the class, and
|
||||||
* the settings the topology layer reads. E-mail accounts and messages are
|
* the settings the topology layer reads. E-mail: the server's accounts as
|
||||||
* a later layer.
|
* the file's bean list has them and as kontenLaden() reads them from
|
||||||
|
* /mailserver/konten.txt (what Java uses from the start of a simulation);
|
||||||
|
* the client's folders and address book, and the account laden() reads
|
||||||
|
* from /konten.txt when its window opens.
|
||||||
*/
|
*/
|
||||||
static List<Object> applications(InternetKnotenBetriebssystem system) {
|
static List<Object> applications(InternetKnotenBetriebssystem system) {
|
||||||
List<Object> result = new ArrayList<>();
|
List<Object> result = new ArrayList<>();
|
||||||
@@ -318,6 +327,41 @@ public class FlsOracle {
|
|||||||
if (application instanceof DNSServer dns) json.put("recursiveResolutionEnabled", dns.isRecursiveResolutionEnabled());
|
if (application instanceof DNSServer dns) json.put("recursiveResolutionEnabled", dns.isRecursiveResolutionEnabled());
|
||||||
if (application instanceof WebServer web) json.put("useVHost", web.isUseVHost());
|
if (application instanceof WebServer web) json.put("useVHost", web.isUseVHost());
|
||||||
if (application instanceof PeerToPeerAnwendung peer) json.put("maxTeilnehmerZahl", peer.getMaxTeilnehmerZahl());
|
if (application instanceof PeerToPeerAnwendung peer) json.put("maxTeilnehmerZahl", peer.getMaxTeilnehmerZahl());
|
||||||
|
if (application instanceof EmailServer mail) {
|
||||||
|
json.put("aktiv", mail.isAktiv());
|
||||||
|
json.put("mailDomain", mail.getMailDomain());
|
||||||
|
json.put("beanAccounts", emailAccounts(mail.getListeBenutzerkonten()));
|
||||||
|
boolean hasFile = system.getDateisystem().holeDatei("/mailserver/konten.txt") != null;
|
||||||
|
Object fileAccounts = null;
|
||||||
|
if (hasFile) {
|
||||||
|
try {
|
||||||
|
mail.kontenLaden();
|
||||||
|
fileAccounts = emailAccounts(mail.getListeBenutzerkonten());
|
||||||
|
} catch (RuntimeException error) {
|
||||||
|
fileAccounts = "!" + error.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json.put("fileAccounts", fileAccounts);
|
||||||
|
}
|
||||||
|
if (application instanceof EmailAnwendung mail) {
|
||||||
|
json.put("received", emails(mail.getEmpfangeneNachrichten()));
|
||||||
|
json.put("sent", emails(mail.getGesendeteNachrichten()));
|
||||||
|
json.put("drafts", emails(mail.getErstellteNachrichten()));
|
||||||
|
List<Object> contacts = new ArrayList<>();
|
||||||
|
for (Kontakt contact : mail.getAdressbuch()) {
|
||||||
|
contacts.add(Arrays.asList(contact.getName(), contact.getVorname(), contact.getStrasse(),
|
||||||
|
contact.getHausnr(), contact.getPlz(), contact.getWohnort(), contact.getEmail(),
|
||||||
|
contact.getTelefon()));
|
||||||
|
}
|
||||||
|
json.put("addressBook", contacts);
|
||||||
|
try {
|
||||||
|
mail.laden();
|
||||||
|
Map<String, EmailKonto> accounts = mail.holeKontoListe();
|
||||||
|
json.put("fileAccount", accounts.isEmpty() ? null : emailAccounts(new ArrayList<>(accounts.values())).get(0));
|
||||||
|
} catch (RuntimeException error) {
|
||||||
|
json.put("fileAccount", "!" + error.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
if (application instanceof Firewall firewall) {
|
if (application instanceof Firewall firewall) {
|
||||||
json.put("activated", firewall.isActivated());
|
json.put("activated", firewall.isActivated());
|
||||||
json.put("defaultPolicy", (int) firewall.getDefaultPolicy());
|
json.put("defaultPolicy", (int) firewall.getDefaultPolicy());
|
||||||
@@ -331,6 +375,61 @@ public class FlsOracle {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<Object> emailAccounts(List<EmailKonto> accounts) {
|
||||||
|
List<Object> result = new ArrayList<>();
|
||||||
|
for (EmailKonto account : accounts) {
|
||||||
|
Map<String, Object> json = new LinkedHashMap<>();
|
||||||
|
json.put("benutzername", account.getBenutzername());
|
||||||
|
json.put("passwort", account.getPasswort());
|
||||||
|
json.put("nachname", account.getNachname());
|
||||||
|
json.put("vorname", account.getVorname());
|
||||||
|
json.put("emailAdresse", account.getEmailAdresse());
|
||||||
|
json.put("pop3server", account.getPop3server());
|
||||||
|
json.put("pop3port", account.getPop3port());
|
||||||
|
json.put("smtpserver", account.getSmtpserver());
|
||||||
|
json.put("smtpport", account.getSmtpport());
|
||||||
|
json.put("nachrichten", emails(account.getNachrichten()));
|
||||||
|
result.add(json);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Object> emails(List<Email> emails) {
|
||||||
|
List<Object> result = new ArrayList<>();
|
||||||
|
for (Email email : emails) {
|
||||||
|
Map<String, Object> json = new LinkedHashMap<>();
|
||||||
|
json.put("absender", address(email.getAbsender()));
|
||||||
|
json.put("empfaenger", addresses(email.getEmpfaenger()));
|
||||||
|
json.put("cc", addresses(email.getCc()));
|
||||||
|
json.put("bcc", addresses(email.getBcc()));
|
||||||
|
json.put("betreff", email.getBetreff());
|
||||||
|
json.put("text", email.getText());
|
||||||
|
json.put("dateReceived", email.getDateReceived());
|
||||||
|
json.put("neu", email.getNeu());
|
||||||
|
json.put("delete", email.getDelete());
|
||||||
|
json.put("versendet", email.isVersendet());
|
||||||
|
result.add(json);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Older files keep recipients as strings: the list is typed, but the
|
||||||
|
* decoder does not check, so an element may be a String.
|
||||||
|
*/
|
||||||
|
static List<Object> addresses(List<AddressEntry> entries) {
|
||||||
|
List<Object> result = new ArrayList<>();
|
||||||
|
for (Object entry : entries) {
|
||||||
|
if (entry == null) result.add(null);
|
||||||
|
else result.add(entry instanceof AddressEntry address ? address(address) : Arrays.asList("string", entry.toString()));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Object> address(AddressEntry entry) {
|
||||||
|
return entry == null ? null : Arrays.asList(entry.getName(), entry.getMailAddress());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A file system tree: a directory is a node whose user object is its name,
|
* A file system tree: a directory is a node whose user object is its name,
|
||||||
* a file one whose user object is a Datei. Contents as SHA-256 and length
|
* a file one whose user object is a Datei. Contents as SHA-256 and length
|
||||||
|
|||||||
Reference in New Issue
Block a user