fix(fls): follow Java's formats, not Java's errors
Java stays the reference for the .fls format, but its errors are no longer copied. The Java oracle still measures Java as it is; each deliberate departure is named in the tests with its reason (JAVA_BUGS) and must really depart. The corpus - what Java itself writes - still reads exactly as in Java. - /dns/hosts: a label may start with a digit, holds no backslash, and the last one is not all digits (RFC 1123, RFC 3696); A, MX and NS are read in any case (RFC 4343); a negative TTL is invalid, so 3600 (RFC 2181). Also for the e-mail server's domain. - /mailserver/konten.txt: empty fields count (Java's StringTokenizer skips them and shifts the rest, so an empty password breaks the account); a short line alone is left out, with a warning (Java throws for the whole file); a message keeps an empty text or subject (Java's split loses it); a ";" in an unescaped subject stays in the message; CRLF lines. - /konten.txt: the last complete line is the account, also with an empty address (Java takes the last line and loses the account); CRLF lines. - Static DHCP assignments: a broken entry alone is left out (Java loses every entry after it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,10 +7,12 @@
|
||||
// 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).
|
||||
// Checked against Java on the corpus (corpus.java.json, applications) and on
|
||||
// the account files of test/email-*.cases.txt, where Java's errors in reading
|
||||
// them are not copied (see readEmailServerFile, readEmailClientFile).
|
||||
|
||||
import { isJavaObject, listItems, simpleClassName, type JavaObject, type JavaValue } from './javabeans.js';
|
||||
import { javaSplit, javaTokens, javaTrim } from './javaString.js';
|
||||
import { javaSplit, javaTrim } from './javaString.js';
|
||||
|
||||
/** AddressEntry: a display name, an address, or both. */
|
||||
export interface JavaAddressEntry {
|
||||
@@ -63,11 +65,6 @@ export interface JavaContact {
|
||||
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';
|
||||
|
||||
@@ -213,17 +210,25 @@ function addressEntries(raw: string): JavaAddressEntry[] {
|
||||
|
||||
/**
|
||||
* `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.
|
||||
* user;domain;password;last name;first name;messages. Messages are
|
||||
* "#"-separated, seven "$"-separated fields each: sender, to, cc, bcc, date,
|
||||
* subject, text. Java's errors are not copied (each named in
|
||||
* javaEmail.test.ts): its StringTokenizer skips empty fields, so an empty
|
||||
* password shifts the rest; one line with fewer than five fields makes it
|
||||
* throw for the whole file; its split drops trailing empty fields, so a
|
||||
* message with an empty text or subject is lost; a CR of a CRLF line stays.
|
||||
* Here fields count also when empty, a short line alone is left out, and a
|
||||
* message needs its seven fields, empty or not.
|
||||
*/
|
||||
export function readJavaEmailServerFile(content: string): JavaEmailAccount[] | JavaFailure {
|
||||
export function readEmailServerFile(content: string, warn: Warn): JavaEmailAccount[] {
|
||||
const accounts: JavaEmailAccount[] = [];
|
||||
for (const line of javaTokens(content, '\n')) {
|
||||
const fields = javaTokens(line, ';');
|
||||
if (fields.length < 5) return { exception: 'NoSuchElementException' };
|
||||
lines(content).forEach((line, index) => {
|
||||
if (line === '') return;
|
||||
const fields = splitAtMost(line, ';', 6);
|
||||
if (fields.length < 5) {
|
||||
warn(`${EMAIL_SERVER_FILE}: line ${index + 1} has fewer than five fields, left out`);
|
||||
return;
|
||||
}
|
||||
const [username, , password, lastName, firstName, messages] = fields as [string, string, string, string, string, string?];
|
||||
accounts.push({
|
||||
username,
|
||||
@@ -235,16 +240,20 @@ export function readJavaEmailServerFile(content: string): JavaEmailAccount[] | J
|
||||
pop3Port: null,
|
||||
smtpServer: null,
|
||||
smtpPort: null,
|
||||
messages: messages === undefined ? [] : fileMessages(messages),
|
||||
messages: messages === undefined ? [] : fileMessages(messages, username, warn),
|
||||
});
|
||||
}
|
||||
});
|
||||
return accounts;
|
||||
}
|
||||
|
||||
function fileMessages(messages: string): JavaEmail[] {
|
||||
return javaSplit(messages, '#').flatMap((message) => {
|
||||
const fields = javaSplit(message, '$');
|
||||
if (fields.length !== 7) return [];
|
||||
function fileMessages(messages: string, username: string, warn: Warn): JavaEmail[] {
|
||||
return messages.split('#').flatMap((message) => {
|
||||
if (message === '') return [];
|
||||
const fields = message.split('$');
|
||||
if (fields.length !== 7) {
|
||||
warn(`${EMAIL_SERVER_FILE}: a message of '${username}' has ${fields.length} fields, not seven, left out`);
|
||||
return [];
|
||||
}
|
||||
const [sender, to, cc, bcc, dateReceived, subject, body] = fields as [string, string, string, string, string, string, string];
|
||||
return [
|
||||
{
|
||||
@@ -263,6 +272,17 @@ function fileMessages(messages: string): JavaEmail[] {
|
||||
});
|
||||
}
|
||||
|
||||
/** The lines of a text file, LF or CRLF. */
|
||||
function lines(content: string): string[] {
|
||||
return content.split('\n').map((line) => (line.endsWith('\r') ? line.slice(0, -1) : line));
|
||||
}
|
||||
|
||||
/** Split at a separator into at most `limit` parts, empty ones included; the last keeps the rest. */
|
||||
function splitAtMost(text: string, separator: string, limit: number): string[] {
|
||||
const parts = text.split(separator);
|
||||
return parts.length <= limit ? parts : [...parts.slice(0, limit - 1), parts.slice(limit - 1).join(separator)];
|
||||
}
|
||||
|
||||
/** brtonl, then undoSpecialChar: "|" is a line break, "&&00124&&" a "|". */
|
||||
function unescapedBody(body: string): string {
|
||||
return body
|
||||
@@ -274,31 +294,36 @@ function unescapedBody(body: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* `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.
|
||||
* `EmailAnwendung.laden`: an account of nine ";"-separated fields — POP3
|
||||
* server, SMTP server, POP3 port, SMTP port, user, password, last name,
|
||||
* first name, address; further fields are ignored. Java takes the last line
|
||||
* whatever it holds; its errors are not copied (each named in
|
||||
* javaEmail.test.ts): a last line with fewer fields — also a complete one
|
||||
* whose address is empty, since split drops trailing empty fields — leaves
|
||||
* the account empty, and a CR of a CRLF line stays in the address. Here the
|
||||
* last line with all nine fields is the account. Null when there is none.
|
||||
*/
|
||||
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: [],
|
||||
};
|
||||
export function readEmailClientFile(content: string, warn: Warn): JavaEmailAccount | null {
|
||||
let account: JavaEmailAccount | null = null;
|
||||
lines(content).forEach((line, index) => {
|
||||
const fields = line.split(';');
|
||||
if (fields.length < 9) {
|
||||
if (javaTrim(line) !== '') warn(`${EMAIL_CLIENT_FILE}: line ${index + 1} is no account of nine fields, ignored`);
|
||||
return;
|
||||
}
|
||||
const [pop3Server, smtpServer, pop3Port, smtpPort, username, password, lastName, firstName, emailAddress] = fields as string[];
|
||||
account = {
|
||||
username: username!,
|
||||
password: password!,
|
||||
lastName: lastName!,
|
||||
firstName: firstName!,
|
||||
emailAddress: emailAddress!,
|
||||
pop3Server: pop3Server!,
|
||||
pop3Port: pop3Port!,
|
||||
smtpServer: smtpServer!,
|
||||
smtpPort: smtpPort!,
|
||||
messages: [],
|
||||
};
|
||||
});
|
||||
return account;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user