diff --git a/CHANGES.md b/CHANGES.md index 9c467b6..55eba05 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -48,6 +48,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand | `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` im Format von Javas Lesern, ohne ihre Fehler (leere Felder zählen, eine kurze Zeile fällt allein weg, Nachrichten mit leerem Text oder Betreff bleiben, `;` im Betreff bleibt, CRLF, beim Client die letzte vollständige Zeile); Empfänger älterer Dateien als Zeichenketten; gegen Java auf dem Korpus und an 43 Fällen geprüft (13 benannte Java-Fehler) | | `src/importEmail.ts` | `Persistence/TopologyProjectStore.swift` (`registerJavaEmailConfigurations`, `assignedJavaEmailMessages`) | übertragen, Eingabe ist Javas Sicht: Server-Konten aus `/mailserver/konten.txt`, ohne Datei aus der Bean-Liste (Java verlöre sie); Client-Konto aus `/konten.txt`, ohne Datei aus der älteren `kontoListe` (Filius 2.14 liest sie nicht mehr); Konten und Nachrichten bleiben, wo Swift verwirft, was seine Prüfung nicht besteht; Vor- und Nachname getrennt; Empfangszeit nur aus der Datei (Swift nummeriert); gegen Swifts Modell geprüft | +| `test/importEmail.test.ts`, `test/email-*.swift.json` | `State/TopologyEmail.swift` (`decodeJavaServer`, `decodeJavaClient`) als Vergleich | neu: dieselben 43 Kontodateien mit Swifts Leser; wie Swift bis auf benannte Fälle, in denen Swift prüft und die ganze Datei verwirft, wo Java liest | | `src/javaFileSystem.ts`, `src/javaString.ts` | Java `Dateisystem.holeDatei`, `String.trim`/`split`/`equalsIgnoreCase` | 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, wie Java bis auf 7 benannte Java-Fehler, das Modell wie Swift bis auf benannte Fälle mit Grund | diff --git a/packages/fls/test/email-client.swift.json b/packages/fls/test/email-client.swift.json new file mode 100644 index 0000000..48ea20d --- /dev/null +++ b/packages/fls/test/email-client.swift.json @@ -0,0 +1,129 @@ +[ + [ + "mail.gmx.de;mail.gmx.de;110;25;herbert;herbert1234;Schmidt;Herbert;herbert@gmx.de", + { + "email" : "herbert@gmx.de", + "name" : "Herbert Schmidt", + "password" : "herbert1234", + "pop3Host" : "mail.gmx.de", + "pop3Port" : 110, + "smtpHost" : "mail.gmx.de", + "smtpPort" : 25, + "username" : "herbert" + } + ], + [ + "mail.gmx.de;mail.gmx.de;110;25;herbert;herbert1234;Schmidt;Herbert;herbert@gmx.de\\u000A", + { + "email" : "herbert@gmx.de", + "name" : "Herbert Schmidt", + "password" : "herbert1234", + "pop3Host" : "mail.gmx.de", + "pop3Port" : 110, + "smtpHost" : "mail.gmx.de", + "smtpPort" : 25, + "username" : "herbert" + } + ], + [ + "a;a;110;25;first;p;L;F;first@x\\u000Ab;b;110;25;second;p;L;F;second@x\\u000A", + { + "email" : "first@x", + "name" : "F L", + "password" : "p", + "pop3Host" : "a", + "pop3Port" : 110, + "smtpHost" : "a", + "smtpPort" : 25, + "username" : "first" + } + ], + [ + "a;a;110;25;first;p;L;F;first@x\\u000Abroken\\u000A", + { + "email" : "first@x", + "name" : "F L", + "password" : "p", + "pop3Host" : "a", + "pop3Port" : 110, + "smtpHost" : "a", + "smtpPort" : 25, + "username" : "first" + } + ], + [ + "", + "!invalidAccount(\"missing client account\")" + ], + [ + "\\u000A", + "!invalidAccount(\"missing client account\")" + ], + [ + "\\u000A\\u000A\\u000A", + "!invalidAccount(\"missing client account\")" + ], + [ + "a;b;110;25;u;p;L;F;\\u000A", + "!invalidAddress(\"\")" + ], + [ + "a;b;110;25;u;p;L;F;u@x;extra\\u000A", + "!invalidAccount(\"malformed client account\")" + ], + [ + "a;b;110;25;u;p;L;F;u@x\\u000D\\u000A", + { + "email" : "u@x", + "name" : "F L", + "password" : "p", + "pop3Host" : "a", + "pop3Port" : 110, + "smtpHost" : "b", + "smtpPort" : 25, + "username" : "u" + } + ], + [ + "a;;110;25;u;p;;;u@x\\u000A", + "!invalidHost(\"\")" + ], + [ + "a;b;110;25;u;p;L;F;u@x\\u000A\\u000A\\u000A", + { + "email" : "u@x", + "name" : "F L", + "password" : "p", + "pop3Host" : "a", + "pop3Port" : 110, + "smtpHost" : "b", + "smtpPort" : 25, + "username" : "u" + } + ], + [ + "\\u000Aa;b;110;25;u;p;L;F;u@x", + { + "email" : "u@x", + "name" : "F L", + "password" : "p", + "pop3Host" : "a", + "pop3Port" : 110, + "smtpHost" : "b", + "smtpPort" : 25, + "username" : "u" + } + ], + [ + " a ; b ;x;y; u ; p ; L ; F ; u@x \\u000A", + "!invalidAccount(\"malformed client account\")" + ], + [ + ";;;;;;;;", + "!invalidAccount(\"malformed client account\")" + ], + [ + ";;;;;;;;x", + "!invalidAccount(\"malformed client account\")" + ] +] diff --git a/packages/fls/test/email-server.swift.json b/packages/fls/test/email-server.swift.json new file mode 100644 index 0000000..6212754 --- /dev/null +++ b/packages/fls/test/email-server.swift.json @@ -0,0 +1,584 @@ +[ + [ + "bob;filius.de;bob;Nachname;Vorname;\\u000A", + [ + { + "mailbox" : [ + + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;\\u000Aalice;filius.de;alice;A;B;\\u000A", + [ + { + "mailbox" : [ + + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + }, + { + "mailbox" : [ + + ], + "name" : "B A", + "password" : "alice", + "username" : "alice" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname", + "!invalidAccount(\"malformed server account\")" + ], + [ + "bob;;bob;Nachname;Vorname;\\u000A", + "!invalidDomain(\"\")" + ], + [ + "bob;filius.de;bob;Nachname\\u000A", + "!invalidAccount(\"malformed server account\")" + ], + [ + "bob;filius.de;bob;Nachname;Vorname;\\u000Abroken\\u000A", + "!invalidAccount(\"malformed server account\")" + ], + [ + " bob ;filius.de; bob ;Nachname;Vorname;\\u000A", + "!invalidAccount(\" bob \")" + ], + [ + "bob;filius.de;bob;Nachname;Vorname\\u000D\\u000A", + "!invalidAccount(\"malformed server account\")" + ], + [ + "", + [ + + ] + ], + [ + "\\u000A\\u000A", + [ + + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#Alice $bob@filius.de$$$$Hi Bob!$Hi BoB!|\\u000A", + [ + { + "mailbox" : [ + { + "bcc" : [ + + ], + "body" : "Hi BoB!\n", + "cc" : [ + + ], + "deliveryIdentity" : null, + "from" : [ + "Alice", + "alice@senior.de" + ], + "id" : 1, + "isMarkedForDeletion" : false, + "isNew" : true, + "isSent" : false, + "receivedAtMilliseconds" : 1, + "subject" : "Hi Bob!", + "to" : [ + [ + null, + "bob@filius.de" + ] + ] + } + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#Alice $bob@filius.de$$$$Hi Bob!$Hi BoB!|#Alice $bob@filius.de$$$$Hi Bob!$Hi BoB!|\\u000A", + [ + { + "mailbox" : [ + { + "bcc" : [ + + ], + "body" : "Hi BoB!\n", + "cc" : [ + + ], + "deliveryIdentity" : null, + "from" : [ + "Alice", + "alice@senior.de" + ], + "id" : 1, + "isMarkedForDeletion" : false, + "isNew" : true, + "isSent" : false, + "receivedAtMilliseconds" : 1, + "subject" : "Hi Bob!", + "to" : [ + [ + null, + "bob@filius.de" + ] + ] + }, + { + "bcc" : [ + + ], + "body" : "Hi BoB!\n", + "cc" : [ + + ], + "deliveryIdentity" : null, + "from" : [ + "Alice", + "alice@senior.de" + ], + "id" : 2, + "isMarkedForDeletion" : false, + "isNew" : true, + "isSent" : false, + "receivedAtMilliseconds" : 2, + "subject" : "Hi Bob!", + "to" : [ + [ + null, + "bob@filius.de" + ] + ] + } + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;Alice $bob@filius.de$$$$Hi$Text\\u000A", + [ + { + "mailbox" : [ + { + "bcc" : [ + + ], + "body" : "Text", + "cc" : [ + + ], + "deliveryIdentity" : null, + "from" : [ + "Alice", + "alice@senior.de" + ], + "id" : 1, + "isMarkedForDeletion" : false, + "isNew" : true, + "isSent" : false, + "receivedAtMilliseconds" : 1, + "subject" : "Hi", + "to" : [ + [ + null, + "bob@filius.de" + ] + ] + } + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#Alice $bob@filius.de$$$$Hi$\\u000A", + [ + { + "mailbox" : [ + { + "bcc" : [ + + ], + "body" : "", + "cc" : [ + + ], + "deliveryIdentity" : null, + "from" : [ + "Alice", + "alice@senior.de" + ], + "id" : 1, + "isMarkedForDeletion" : false, + "isNew" : true, + "isSent" : false, + "receivedAtMilliseconds" : 1, + "subject" : "Hi", + "to" : [ + [ + null, + "bob@filius.de" + ] + ] + } + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#Alice $bob@filius.de$$$$$\\u000A", + [ + { + "mailbox" : [ + { + "bcc" : [ + + ], + "body" : "", + "cc" : [ + + ], + "deliveryIdentity" : null, + "from" : [ + "Alice", + "alice@senior.de" + ], + "id" : 1, + "isMarkedForDeletion" : false, + "isNew" : true, + "isSent" : false, + "receivedAtMilliseconds" : 1, + "subject" : "", + "to" : [ + [ + null, + "bob@filius.de" + ] + ] + } + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#Alice $bob@filius.de$$$$Hi $5$Text\\u000A", + "!malformedMessage" + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$c@z$d@w$1234$Hi$Text\\u000A", + [ + { + "mailbox" : [ + { + "bcc" : [ + [ + null, + "d@w" + ] + ], + "body" : "Text", + "cc" : [ + [ + null, + "c@z" + ] + ], + "deliveryIdentity" : null, + "from" : [ + null, + "a@x" + ], + "id" : 1, + "isMarkedForDeletion" : false, + "isNew" : true, + "isSent" : false, + "receivedAtMilliseconds" : 1234, + "subject" : "Hi", + "to" : [ + [ + null, + "b@y" + ] + ] + } + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#a@x$b@y, ,c@z,$$$$Hi$Text\\u000A", + "!malformedMessage" + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#Name $Bob , $$$$Hi$Text\\u000A", + [ + { + "mailbox" : [ + { + "bcc" : [ + + ], + "body" : "Text", + "cc" : [ + + ], + "deliveryIdentity" : null, + "from" : [ + null, + "a@x" + ], + "id" : 1, + "isMarkedForDeletion" : false, + "isNew" : true, + "isSent" : false, + "receivedAtMilliseconds" : 1, + "subject" : "Hi", + "to" : [ + [ + "Bob", + "b@y" + ], + [ + null, + "c@z" + ] + ] + } + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#Only Name$b@y$$$$Hi$Text\\u000A", + "!malformedMessage" + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$l1||l2&&00124&&x&&00036&&y&&00059&&z&&00035&&w\\u000A", + [ + { + "mailbox" : [ + { + "bcc" : [ + + ], + "body" : "l1\n\nl2|x$y;z#w", + "cc" : [ + + ], + "deliveryIdentity" : null, + "from" : [ + null, + "a@x" + ], + "id" : 1, + "isMarkedForDeletion" : false, + "isNew" : true, + "isSent" : false, + "receivedAtMilliseconds" : 1, + "subject" : "Hi", + "to" : [ + [ + null, + "b@y" + ] + ] + } + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$&&00124&&|\\u000A", + [ + { + "mailbox" : [ + { + "bcc" : [ + + ], + "body" : "|\n", + "cc" : [ + + ], + "deliveryIdentity" : null, + "from" : [ + null, + "a@x" + ], + "id" : 1, + "isMarkedForDeletion" : false, + "isNew" : true, + "isSent" : false, + "receivedAtMilliseconds" : 1, + "subject" : "Hi", + "to" : [ + [ + null, + "b@y" + ] + ] + } + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$Text;extra\\u000A", + [ + { + "mailbox" : [ + { + "bcc" : [ + + ], + "body" : "Text;extra", + "cc" : [ + + ], + "deliveryIdentity" : null, + "from" : [ + null, + "a@x" + ], + "id" : 1, + "isMarkedForDeletion" : false, + "isNew" : true, + "isSent" : false, + "receivedAtMilliseconds" : 1, + "subject" : "Hi", + "to" : [ + [ + null, + "b@y" + ] + ] + } + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;##a@x$b@y$$$$Hi$Text##\\u000A", + [ + { + "mailbox" : [ + { + "bcc" : [ + + ], + "body" : "Text", + "cc" : [ + + ], + "deliveryIdentity" : null, + "from" : [ + null, + "a@x" + ], + "id" : 1, + "isMarkedForDeletion" : false, + "isNew" : true, + "isSent" : false, + "receivedAtMilliseconds" : 1, + "subject" : "Hi", + "to" : [ + [ + null, + "b@y" + ] + ] + } + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#a@x$b@y$$$$Hi$Text\\u000D\\u000A", + [ + { + "mailbox" : [ + { + "bcc" : [ + + ], + "body" : "Text", + "cc" : [ + + ], + "deliveryIdentity" : null, + "from" : [ + null, + "a@x" + ], + "id" : 1, + "isMarkedForDeletion" : false, + "isNew" : true, + "isSent" : false, + "receivedAtMilliseconds" : 1, + "subject" : "Hi", + "to" : [ + [ + null, + "b@y" + ] + ] + } + ], + "name" : "Vorname Nachname", + "password" : "bob", + "username" : "bob" + } + ] + ], + [ + "bob;filius.de;bob;Nachname;Vorname;#\\u001Fa@x $b@y$$$$Hi$Text\\u000A", + "!malformedMessage" + ] +] diff --git a/packages/fls/test/importEmail.test.ts b/packages/fls/test/importEmail.test.ts new file mode 100644 index 0000000..6762daf --- /dev/null +++ b/packages/fls/test/importEmail.test.ts @@ -0,0 +1,157 @@ +// The e-mail account files as the model takes them, against Swift's reader +// (TopologyRuntimeEmailStorage.decodeJavaServer/decodeJavaClient, +// email-*.swift.json, tools/swift-oracle --email-files) on the cases Java +// read too (javaEmail.test.ts). Swift checks names, hosts and addresses and +// fails the whole file on one it rejects, where Java reads it; each such +// case is named with its reason and must really depart. + +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; +import type { EmailAddress, EmailMessage } from '@mdg-filius/core'; +import { emailClientConfiguration, emailServerConfiguration } from '../src/importEmail.js'; +import { readEmailClientFile, readEmailServerFile } from '../src/javaEmail.js'; +import type { FlsApplication } from '../src/index.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))); +} + +const fullName = (person: { firstName: string; lastName: string }) => + [person.firstName, person.lastName].filter((part) => part !== '').join(' '); + +function swiftMessage(message: EmailMessage) { + const address = (entry: EmailAddress) => [entry.name, entry.mailAddress]; + return { + id: message.id, + from: address(message.from), + to: message.to.map(address), + cc: message.cc.map(address), + bcc: message.bcc.map(address), + subject: message.subject, + body: message.body, + deliveryIdentity: null, + receivedAtMilliseconds: message.receivedAtMilliseconds ?? null, + isNew: message.isNew, + isMarkedForDeletion: message.isMarkedForDeletion, + isSent: message.isSent, + }; +} + +function serverAccounts(content: string) { + const application = { fileAccounts: readEmailServerFile(content, () => {}), mailDomain: 'filius.de' } as FlsApplication; + return emailServerConfiguration(application, () => {}).accounts.map((account) => ({ + username: account.username, + password: account.password, + name: fullName(account), + mailbox: account.mailbox.map(swiftMessage), + })); +} + +function clientAccount(content: string) { + const application = { fileAccount: readEmailClientFile(content, () => {}) } as FlsApplication; + const account = emailClientConfiguration(application, () => {}).account; + if (account === null) return null; + const { firstName, lastName, ...fields } = account; + return { ...fields, name: fullName({ firstName, lastName }) }; +} + +type Departures = { reason: string; lines: string[] }[]; + +/** + * Swift's result where it means the same as ours: Swift numbers a mailbox's + * messages as their received time (a Swift error named in + * importProject.test.ts), and "no account" is an error to Swift. + */ +function comparable(expected: unknown): unknown { + if (expected === '!invalidAccount("missing client account")') return null; + if (!Array.isArray(expected)) return expected; + return expected.map((account: { mailbox: { id: number; receivedAtMilliseconds: number | null }[] }) => ({ + ...account, + mailbox: account.mailbox.map((message) => + message.receivedAtMilliseconds === message.id ? { ...message, receivedAtMilliseconds: null } : message, + ), + })); +} + +function compareWithSwift(kind: 'server' | 'client', departures: Departures, read: (content: string) => unknown) { + const cases = JSON.parse(readFileSync(join(here, `email-${kind}.swift.json`), 'utf8')) as [string, unknown][]; + const lines = readFileSync(join(here, `email-${kind}.cases.txt`), 'ascii').split('\n').slice(0, -1); + const byLine = new Map(departures.flatMap(({ reason, lines: named }) => named.map((line) => [line, reason] as const))); + it('covers every line of the cases file and names only its lines', () => { + expect(cases.map(([line]) => line)).toEqual(lines); + expect([...byLine.keys()].filter((line) => !lines.includes(line))).toEqual([]); + }); + for (const [line, expected] of cases) { + const reason = byLine.get(line); + it(reason === undefined ? JSON.stringify(line) : `${JSON.stringify(line)} (departs: ${reason})`, () => { + if (reason === undefined) expect(read(unescape(line))).toEqual(comparable(expected)); + else expect(read(unescape(line))).not.toEqual(comparable(expected)); + }); + } +} + +const SERVER_DEPARTURES: Departures = [ + { + reason: 'a line without the messages field is an account, as in Java; Swift wants the sixth field and fails the file', + lines: [String.raw`bob;filius.de;bob;Nachname;Vorname`, String.raw`bob;filius.de;bob;Nachname;Vorname\u000D\u000A`], + }, + { + reason: "the file's domain column is not used (Java takes the server's); Swift fails the file on an empty one", + lines: [String.raw`bob;;bob;Nachname;Vorname;\u000A`], + }, + { + reason: 'a short line alone is left out; Swift fails the whole file', + lines: [String.raw`bob;filius.de;bob;Nachname\u000A`, String.raw`bob;filius.de;bob;Nachname;Vorname;\u000Abroken\u000A`], + }, + { + reason: 'a user name stays as Java reads it; Swift rejects one with spaces and fails the file', + lines: [String.raw` bob ;filius.de; bob ;Nachname;Vorname;\u000A`], + }, + { + reason: 'a message without seven fields alone is left out; Swift fails the whole file', + lines: [String.raw`bob;filius.de;bob;Nachname;Vorname;#Alice $bob@filius.de$$$$Hi $5$Text\u000A`], + }, + { + reason: "addresses as Java reads them (blank entries skipped, a name alone, Java's trim); Swift fails the file", + lines: [ + String.raw`bob;filius.de;bob;Nachname;Vorname;#a@x$b@y, ,c@z,$$$$Hi$Text\u000A`, + String.raw`bob;filius.de;bob;Nachname;Vorname;#Name { + compareWithSwift('server', SERVER_DEPARTURES, serverAccounts); +}); + +describe('the client account file against Swift', () => { + compareWithSwift('client', CLIENT_DEPARTURES, clientAccount); +}); diff --git a/tools/swift-oracle/README.md b/tools/swift-oracle/README.md index 06998a3..40823b6 100644 --- a/tools/swift-oracle/README.md +++ b/tools/swift-oracle/README.md @@ -38,6 +38,10 @@ Sortierung, Base64 — und die ganzen Mengen `whitespacesAndNewlines` und unter Linux; ihre Steuerzeichen-Tabelle hat in Ebene 14 einen Fehler, den der Port nicht übernimmt. +`--email-files server|client` liest die Kontodateien +(`packages/fls/test/email-*.cases.txt`) mit Swifts +`TopologyRuntimeEmailStorage.decodeJavaServer`/`decodeJavaClient`. + `--dns-hosts` liest dieselben `/dns/hosts`-Fälle wie das Java-Orakel (`packages/fls/test/dns-hosts.cases.txt`) mit Swifts `TopologyDNSHostsFileCodec`; unter `state` stehen die DNS-Einträge jedes diff --git a/tools/swift-oracle/Sources/Oracle/main.swift b/tools/swift-oracle/Sources/Oracle/main.swift index ebd5577..38a4458 100644 --- a/tools/swift-oracle/Sources/Oracle/main.swift +++ b/tools/swift-oracle/Sources/Oracle/main.swift @@ -666,7 +666,7 @@ func dnsRecordJSON(_ record: TopologyDNSResourceRecord) -> [Any] { [record.name.rawValue, record.type.rawValue, Int(record.ttlSeconds), record.target] } -/// One case per line: a whole hosts file, ASCII with Java-style unicode +/// One case per line: a whole file (hosts, e-mail accounts), ASCII with Java-style unicode /// escapes (backslash, u, four hex digits) — the file the Java oracle reads. func readDNSHostsCases(_ url: URL) throws -> [(line: String, text: String)] { let lines = try String(contentsOf: url, encoding: .ascii).components(separatedBy: "\n") @@ -722,6 +722,37 @@ if CommandLine.arguments.count == 4, CommandLine.arguments[1] == "--dns-hosts" { exit(0) } +if CommandLine.arguments.count == 5, CommandLine.arguments[1] == "--email-files" { + let server = CommandLine.arguments[2] == "server" + let cases = try readDNSHostsCases(URL(fileURLWithPath: CommandLine.arguments[3])) + let output: [[Any]] = cases.map { item in + do { + if server { + let configuration = try TopologyRuntimeEmailStorage.decodeJavaServer(item.text) + return [item.line, configuration.accounts.map { account in + [ + "username": account.username, "password": account.password, "name": account.name, + "mailbox": account.mailbox.map(emailMessageJSON), + ] as JSON + }] + } + let client = try TopologyRuntimeEmailStorage.decodeJavaClient(item.text) + return [item.line, [ + "pop3Host": client.pop3Host, "pop3Port": client.pop3Port, + "smtpHost": client.smtpHost, "smtpPort": client.smtpPort, + "username": client.username, "password": client.password, + "name": client.name, "email": client.email, + ] as JSON] + } catch { + return [item.line, "!\(error)"] + } + } + let data = try JSONSerialization.data(withJSONObject: output, options: [.prettyPrinted, .sortedKeys]) + try (data + Data("\n".utf8)).write(to: URL(fileURLWithPath: CommandLine.arguments[4])) + print("email \(CommandLine.arguments[2]) files: \(cases.count) cases") + exit(0) +} + if CommandLine.arguments.count == 4, CommandLine.arguments[1] == "--corpus" { let summary = try readCorpus(root: URL(fileURLWithPath: CommandLine.arguments[2])) let data = try JSONSerialization.data(withJSONObject: summary, options: [.prettyPrinted, .sortedKeys]) diff --git a/tools/swift-oracle/run.sh b/tools/swift-oracle/run.sh index 5d5b300..96250d5 100755 --- a/tools/swift-oracle/run.sh +++ b/tools/swift-oracle/run.sh @@ -15,4 +15,6 @@ docker run --rm -v "$root:/work" -w /work/tools/swift-oracle swift:6.1-jammy sh .build/debug/Oracle --vfs /work/packages/core/test/vfs.cases.json /work/packages/core/test/vfs.swift.json .build/debug/Oracle --strings /work/packages/core/test/strings.cases.json /work/packages/core/test/strings.swift.json .build/debug/Oracle --path-folding /work/packages/fls/test/path-folding.cases.json /work/packages/fls/test/path-folding.swift.json - .build/debug/Oracle --dns-hosts /work/packages/fls/test/dns-hosts.cases.txt /work/packages/fls/test/dns-hosts.swift.json' + .build/debug/Oracle --dns-hosts /work/packages/fls/test/dns-hosts.cases.txt /work/packages/fls/test/dns-hosts.swift.json + .build/debug/Oracle --email-files server /work/packages/fls/test/email-server.cases.txt /work/packages/fls/test/email-server.swift.json + .build/debug/Oracle --email-files client /work/packages/fls/test/email-client.cases.txt /work/packages/fls/test/email-client.swift.json'