Files
webmail/lib/attachment-text/types.ts
T
Linus Rath 7c97eb2691 fix: keep attachment text reads within their size and character limits
Every reader now stops at the read's character budget, a cell at 4,000
characters, one Office XML part at 12 MB, and parts with a DTD are
refused. A compound file named .docx is encrypted only when it holds an
EncryptedPackage stream, else unsupported; .eml files are unsupported
instead of raw MIME; maxChars that isn't a number falls back to 20,000.

attachment-text.test.ts: the zip bomb test passes the cap as an option
now, and a bare compound file named secret.docx is expected unsupported
(it is a legacy file under a new name; limits.test.ts covers the
password-protected one).
2026-11-02 23:10:51 +01:00

61 lines
2.5 KiB
TypeScript

// Shapes and limits of the text extraction for attachments and files.
// Pure: no imports from the app, so the plugin host and Files can share it.
/** What kind of file the text came from. */
export type TextFormat =
| 'text' | 'csv' | 'html' | 'ics' | 'vcf' | 'pdf'
| 'docx' | 'xlsx' | 'pptx' | 'odt' | 'ods' | 'odp' | 'other';
/**
* ok: `text` holds the text. too_big: over the size limit, not read.
* unsupported: a format without a reader (images, legacy .doc/.xls/.ppt,
* archives). no_text: nothing to read, e.g. a scanned PDF. encrypted:
* protected by a password. failed: the file is damaged or reading broke.
*/
export type TextStatus = 'ok' | 'too_big' | 'unsupported' | 'no_text' | 'encrypted' | 'failed';
export interface ExtractedText {
format: TextFormat;
status: TextStatus;
/** The text, '' unless status is ok. */
text: string;
/** True when the text was cut at maxChars, or a PDF or sheet had more than was read. */
truncated: boolean;
/** PDFs: the number of pages in the file. */
pages?: number;
/** Spreadsheets: the names of the sheets read. */
sheets?: string[];
}
export interface ExtractOptions {
/** File name; its extension picks the format first. */
name?: string;
/** Media type, used when the name says nothing. */
type?: string;
/** Longest text returned (default DEFAULT_TEXT_CHARS, at most MAX_TEXT_CHARS). */
maxChars?: number;
}
/** Text returned when the caller doesn't ask for a length. */
export const DEFAULT_TEXT_CHARS = 20_000;
/** Longest text one read returns. */
export const MAX_TEXT_CHARS = 100_000;
/** Largest file read for its text. */
export const MAX_TEXT_SOURCE_BYTES = 20 * 1024 * 1024;
/** Pages of a PDF read at most. */
export const MAX_PDF_PAGES = 50;
/** Fewer characters per page than this on average: the PDF has no text layer. */
export const MIN_PDF_CHARS_PER_PAGE = 20;
/** Sheets of a spreadsheet read at most. */
export const MAX_SHEETS = 5;
/** Rows per sheet read at most. */
export const MAX_SHEET_ROWS = 2_000;
/** Bytes an Office file may unpack to, all entries read together (zip bombs). */
export const MAX_INFLATED_BYTES = 50 * 1024 * 1024;
/** Bytes one XML part of an Office file may unpack to: DOMParser holds it all in memory. */
export const MAX_XML_PART_BYTES = 12 * 1024 * 1024;
/** Characters of one spreadsheet cell, after which it is cut. */
export const MAX_CELL_CHARS = 4_000;
/** Readers stop this many characters past maxChars, so the final cut can end at a word. */
export const TEXT_BUDGET_SLACK = 2_000;