From e39865569c043afa6ad366a81d509e15ecf7620c Mon Sep 17 00:00:00 2001 From: Aria Kamani <271932911+prashantkamani@users.noreply.github.com> Date: Wed, 13 May 2026 00:15:39 -0700 Subject: [PATCH 1/2] feat(contacts): container-level filtering + unified contact privacy (v3.7.22) Wire ItemFilter in ContactsCLI to filter by CNContainer (account name), matching the existing pattern in CalendarCLI and ReminderCLI. - containers subcommand: lists all contact account containers - Container filtering for all verbs via ContactAccessMode strategy pattern - --container flag on create for targeting specific accounts - Unified contact privacy: unifyResults=false prevents cross-account data leaks - Scoped get with relatedContacts for authorized cross-account linkage - Backing ID enforcement: update/delete reject multi-source unified IDs - Exchange ghost group resolution in allAccountContainers() - Search optimization: name predicate + container post-filter - JS handler layer: containers action, schema, tool-args, command docs - MCP tests + eval scenarios for containers action - Pure-logic Swift tests (zero TCC dependency for CI safety) - Version bump to 3.7.22 across all 5 package files Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- README.md | 13 +- commands/contacts.md | 12 +- docs/multi-agent-setup.md | 25 +- .../fixtures/contact/containers-results.json | 19 + evals/scenarios/tool-call-correctness.yaml | 14 + evals/tests/safety.test.js | 2 +- lib/handlers/contact.js | 3 + lib/schemas.js | 5 +- lib/tool-args.js | 1 + mcp-server/dist/server.js | 5377 +++++++++++++---- mcp-server/package.json | 2 +- mcp-server/test/tool-args.test.js | 18 + openclaw/openclaw.plugin.json | 2 +- openclaw/package.json | 2 +- swift/Sources/ContactsCLI/ContactsCLI.swift | 670 +- .../ContainerFilterTests.swift | 47 + .../Tests/MailCLITests/MIMEBuilderTests.swift | 6 +- 19 files changed, 4957 insertions(+), 1265 deletions(-) create mode 100644 evals/fixtures/contact/containers-results.json create mode 100644 swift/Tests/ContactsCLITests/ContainerFilterTests.swift diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 805fbab..d6fae48 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -8,7 +8,7 @@ "name": "apple-pim", "source": "./", "description": "Native macOS integration for Calendar, Reminders, Contacts, and Mail using EventKit, Contacts, and JXA frameworks", - "version": "3.7.20", + "version": "3.7.22", "keywords": [ "calendar", "reminders", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 3b02675..65766af 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "apple-pim", - "version": "3.7.20", + "version": "3.7.22", "description": "Native macOS integration for Calendar, Reminders, Contacts, and Mail using EventKit, Contacts, and JXA frameworks", "author": { "name": "Omar Shahine", diff --git a/README.md b/README.md index a1658fa..45e0706 100644 --- a/README.md +++ b/README.md @@ -190,10 +190,12 @@ Config files are stored at `~/.config/apple-pim/`: ### Filter Modes -- **allowlist**: Only listed calendars/lists are accessible +- **allowlist**: Only listed items are accessible - **blocklist**: All EXCEPT listed items are accessible - **all**: No filtering (default if no config file exists) +Filtering applies per domain: calendars filter by calendar name, reminders by list name, contacts by account container name (e.g. "iCloud", "Work Exchange", "Personal Gmail"). Use `contacts-cli containers` to see available account names. + ### Profiles Profiles let you give different agents different access to your PIM data. Each profile overrides specific domain sections from the base config — fields not in the profile are inherited from the base. @@ -213,6 +215,11 @@ Profiles let you give different agents different access to your PIM data. Each p "mode": "allowlist", "items": ["Work"] }, + "contacts": { + "enabled": true, + "mode": "allowlist", + "items": ["Work Exchange"] + }, "mail": { "enabled": false }, @@ -364,7 +371,9 @@ calendar-cli events --from today --to tomorrow calendar-cli create --title "Lunch" --start "tomorrow 12pm" --duration 60 reminder-cli lists reminder-cli items --list "Personal" --filter overdue +contacts-cli containers # List contact account containers contacts-cli search "John" +contacts-cli search "John" --profile work # Scoped to work contacts only mail-cli messages --mailbox INBOX --limit 10 mail-cli send --to "user@example.com" --subject "Hello" --body "Message" mail-cli send --to "user@example.com" --subject "Report" --body "See attached" --attachment ~/report.pdf @@ -460,7 +469,7 @@ mail-cli secrets set smtp.icloud.password --store openclaw |------|---------|--------| | `calendar` / `apple_pim_calendar` | `list`, `events`, `get`, `search`, `create`, `update`, `delete`, `batch_create` | Calendar events via EventKit | | `reminder` / `apple_pim_reminder` | `lists`, `items`, `get`, `search`, `create`, `complete`, `update`, `delete`, `batch_create`, `batch_complete`, `batch_delete` | Reminders via EventKit | -| `contact` / `apple_pim_contact` | `groups`, `list`, `search`, `get`, `create`, `update`, `delete` | Contacts framework | +| `contact` / `apple_pim_contact` | `containers`, `groups`, `list`, `search`, `get`, `create`, `update`, `delete` | Contacts framework | | `mail` / `apple_pim_mail` | `accounts`, `mailboxes`, `messages`, `get`, `search`, `send`, `reply`, `save_attachment`, `update`, `move`, `delete`, `batch_update`, `batch_delete`, `auth_check` | Mail.app via JXA/AppleScript | | `apple-pim` / `apple_pim_system` | `status`, `authorize`, `config_show`, `config_init` | Authorization & configuration | diff --git a/commands/contacts.md b/commands/contacts.md index 8965057..a0d8cb3 100644 --- a/commands/contacts.md +++ b/commands/contacts.md @@ -1,6 +1,6 @@ --- description: Manage macOS contacts - list, search, get details, create, update, delete -argument-hint: "[groups|list|search|get|create|update|delete] [options]" +argument-hint: "[containers|groups|list|search|get|create|update|delete] [options]" allowed-tools: - mcp__apple-pim__contact --- @@ -13,6 +13,9 @@ Manage contacts using the Apple Contacts framework. When the user runs this command, determine which operation they need and use the `contact` tool with the appropriate action: +### List Containers +Use `contact` with action `containers` to show all contact accounts (iCloud, Exchange, Google, etc.). + ### List Groups Use `contact` with action `groups` to show all contact groups. @@ -35,6 +38,7 @@ Use `contact` with action `get` to get full details for a contact: ### Create Contact Use `contact` with action `create` to create a new contact: - Optional: `name` (full name) OR `firstName`/`lastName` +- Optional: `container` (target account name — use `containers` to see available accounts) - Optional: `email`, `phone`, `organization`, `jobTitle`, `notes`, `birthday` ### Update Contact @@ -54,6 +58,11 @@ Use `contact` with action `delete` to remove a contact: ## Examples +**List contact accounts:** +``` +/apple-pim:contacts containers +``` + **List contact groups:** ``` /apple-pim:contacts groups @@ -82,6 +91,7 @@ Use `contact` with action `delete` to remove a contact: ``` /apple-pim:contacts create --name "Jane Doe" --email "jane@example.com" /apple-pim:contacts create --first-name "John" --last-name "Smith" --phone "555-1234" --organization "Acme Corp" +/apple-pim:contacts create --name "Jane Doe" --container "iCloud" --email "jane@example.com" ``` **Update a contact:** diff --git a/docs/multi-agent-setup.md b/docs/multi-agent-setup.md index 2fd25bb..55420cb 100644 --- a/docs/multi-agent-setup.md +++ b/docs/multi-agent-setup.md @@ -52,14 +52,14 @@ Profiles are the simplest approach. All agents share `~/.config/apple-pim/` but "mode": "allowlist", "items": ["Travel"] }, - "mail": { "enabled": false }, "contacts": { "enabled": false }, + "mail": { "enabled": false }, "default_calendar": "Travel", "default_reminder_list": "Travel" } ``` -**`work.json`** — work calendar only: +**`work.json`** — work calendar and work contacts only: ```json { "calendars": { @@ -72,13 +72,31 @@ Profiles are the simplest approach. All agents share `~/.config/apple-pim/` but "mode": "allowlist", "items": ["Work"] }, - "contacts": { "enabled": true }, + "contacts": { + "enabled": true, + "mode": "allowlist", + "items": ["Work Exchange"] + }, "mail": { "enabled": false }, "default_calendar": "Work", "default_reminder_list": "Work" } ``` +**`family.json`** — shared family contacts only: +```json +{ + "calendars": { "enabled": false }, + "reminders": { "enabled": false }, + "contacts": { + "enabled": true, + "mode": "allowlist", + "items": ["Family iCloud"] + }, + "mail": { "enabled": false } +} +``` + ### Assign Profiles **OpenClaw plugin** (recommended — per-call isolation): @@ -114,6 +132,7 @@ Profiles use **whole-section replacement**, not field-level merge: |-------------|---------|--------| | `calendars: {mode: "all"}` | `calendars: {mode: "allowlist", items: ["Work"]}` | Profile's calendars config used entirely | | `reminders: {mode: "allowlist", items: ["A", "B"]}` | *(not specified)* | Base's reminders config inherited | +| `contacts: {mode: "all"}` | `contacts: {mode: "allowlist", items: ["Work Exchange"]}` | Profile's contacts config used entirely | | `default_calendar: "Personal"` | `default_calendar: "Work"` | Profile's default used | ## Strategy 2: Config Directory Isolation diff --git a/evals/fixtures/contact/containers-results.json b/evals/fixtures/contact/containers-results.json new file mode 100644 index 0000000..453098d --- /dev/null +++ b/evals/fixtures/contact/containers-results.json @@ -0,0 +1,19 @@ +{ + "containers": [ + { + "name": "iCloud", + "id": "A1B2C3D4-5678-90AB-CDEF-111111111111", + "type": "CardDAV" + }, + { + "name": "Gmail", + "id": "E5F6A7B8-9012-34CD-EF56-222222222222", + "type": "CardDAV" + }, + { + "name": "Exchange", + "id": "C9D0E1F2-3456-78AB-CD90-333333333333", + "type": "Exchange" + } + ] +} diff --git a/evals/scenarios/tool-call-correctness.yaml b/evals/scenarios/tool-call-correctness.yaml index 2e1327b..ce041d6 100644 --- a/evals/scenarios/tool-call-correctness.yaml +++ b/evals/scenarios/tool-call-correctness.yaml @@ -98,6 +98,20 @@ cases: expect: throws: "Contact ID is required" + - id: contact-containers + tool: contact + description: "Containers action produces correct CLI args" + input: { action: containers } + expect: + cli_args_contain: ["containers"] + + - id: contact-create-with-container + tool: contact + description: "Container param passes --container flag on create" + input: { action: create, name: "Jane Doe", container: "iCloud" } + expect: + cli_args_contain: ["create", "--name", "--container", "iCloud"] + - id: cal-delete-future-events tool: calendar description: "futureEvents flag passed on delete" diff --git a/evals/tests/safety.test.js b/evals/tests/safety.test.js index 9f1ca5b..9cdd698 100644 --- a/evals/tests/safety.test.js +++ b/evals/tests/safety.test.js @@ -124,7 +124,7 @@ describe("Category 4: Safety Properties", () => { const contactTool = tools.find((t) => t.name === "contact"); const actions = contactTool.inputSchema.properties.action.enum; const covered = new Set([ - "groups", "list", "search", "get", "create", "update", "delete", "schema", + "containers", "groups", "list", "search", "get", "create", "update", "delete", "schema", ]); for (const action of actions) { expect(covered.has(action)).toBe(true); diff --git a/lib/handlers/contact.js b/lib/handlers/contact.js index a4bde1e..4c1fc5c 100644 --- a/lib/handlers/contact.js +++ b/lib/handlers/contact.js @@ -10,6 +10,9 @@ export async function handleContact(args, runCLI) { case "groups": return await runCLI("contacts-cli", ["groups"]); + case "containers": + return await runCLI("contacts-cli", ["containers"]); + case "list": cliArgs.push("list"); if (args.group) cliArgs.push("--group", args.group); diff --git a/lib/schemas.js b/lib/schemas.js index 3ca0d5d..f2826aa 100644 --- a/lib/schemas.js +++ b/lib/schemas.js @@ -216,13 +216,13 @@ export const tools = [ { name: "contact", description: - "Manage macOS contacts. Actions: groups (list groups), list (list contacts), search (by name/email/phone), get (by ID with photo), create, update, delete, schema (show input schema).", + "Manage macOS contacts. Actions: containers (list contact accounts), groups (list groups), list (list contacts), search (by name/email/phone), get (by ID with photo), create, update, delete, schema (show input schema).", inputSchema: { type: "object", properties: { action: { type: "string", - enum: ["groups", "list", "search", "get", "create", "update", "delete", "schema"], + enum: ["containers", "groups", "list", "search", "get", "create", "update", "delete", "schema"], description: "Operation to perform", }, ...agentDXProperties, @@ -348,6 +348,7 @@ export const tools = [ }, }, notes: { type: "string" }, + container: { type: "string", description: "Target container/account name for create" }, configDir: { type: "string", description: "Override PIM config directory (OpenClaw only — ignored by MCP server)" }, profile: { type: "string", description: "Override PIM profile name (OpenClaw only — MCP server uses APPLE_PIM_PROFILE env)" }, }, diff --git a/lib/tool-args.js b/lib/tool-args.js index 83491d7..fa43c9e 100644 --- a/lib/tool-args.js +++ b/lib/tool-args.js @@ -111,6 +111,7 @@ function pushContactSharedFields(cliArgs, args) { export function buildContactCreateArgs(args) { const cliArgs = ["create"]; if (args.name) cliArgs.push("--name", args.name); + if (args.container) cliArgs.push("--container", args.container); pushContactSharedFields(cliArgs, args); return cliArgs; } diff --git a/mcp-server/dist/server.js b/mcp-server/dist/server.js index d584bd9..1f1eb50 100755 --- a/mcp-server/dist/server.js +++ b/mcp-server/dist/server.js @@ -4745,6 +4745,7 @@ var require_pattern = __commonJS({ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var code_1 = require_code2(); + var util_1 = require_util(); var codegen_1 = require_codegen(); var error2 = { message: ({ schemaCode }) => (0, codegen_1.str)`must match pattern "${schemaCode}"`, @@ -4757,10 +4758,18 @@ var require_pattern = __commonJS({ $data: true, error: error2, code(cxt) { - const { data, $data, schema, schemaCode, it } = cxt; + const { gen, data, $data, schema, schemaCode, it } = cxt; const u = it.opts.unicodeRegExp ? "u" : ""; - const regExp = $data ? (0, codegen_1._)`(new RegExp(${schemaCode}, ${u}))` : (0, code_1.usePattern)(cxt, schema); - cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data})`); + if ($data) { + const { regExp } = it.opts.code; + const regExpCode = regExp.code === "new RegExp" ? (0, codegen_1._)`new RegExp` : (0, util_1.useFunc)(gen, regExp); + const valid = gen.let("valid"); + gen.try(() => gen.assign(valid, (0, codegen_1._)`${regExpCode}(${schemaCode}, ${u}).test(${data})`), () => gen.assign(valid, false)); + cxt.fail$data((0, codegen_1._)`!${valid}`); + } else { + const regExp = (0, code_1.usePattern)(cxt, schema); + cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data})`); + } } }; exports.default = def; @@ -6881,9 +6890,9 @@ var require_safer = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/lib/bom-handling.js +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/lib/bom-handling.js var require_bom_handling = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/lib/bom-handling.js"(exports) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/lib/bom-handling.js"(exports) { "use strict"; var BOMChar = "\uFEFF"; exports.PrependBOM = PrependBOMWrapper; @@ -6925,9 +6934,9 @@ var require_bom_handling = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/internal.js +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/internal.js var require_internal = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/internal.js"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/internal.js"(exports, module) { "use strict"; var Buffer2 = require_safer().Buffer; module.exports = { @@ -7077,9 +7086,9 @@ var require_internal = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/utf32.js +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/utf32.js var require_utf32 = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/utf32.js"(exports) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/utf32.js"(exports) { "use strict"; var Buffer2 = require_safer().Buffer; exports._utf32 = Utf32Codec; @@ -7302,9 +7311,9 @@ var require_utf32 = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/utf16.js +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/utf16.js var require_utf16 = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/utf16.js"(exports) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/utf16.js"(exports) { "use strict"; var Buffer2 = require_safer().Buffer; exports.utf16be = Utf16BECodec; @@ -7442,9 +7451,9 @@ var require_utf16 = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/utf7.js +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/utf7.js var require_utf7 = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/utf7.js"(exports) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/utf7.js"(exports) { "use strict"; var Buffer2 = require_safer().Buffer; exports.utf7 = Utf7Codec; @@ -7643,9 +7652,9 @@ var require_utf7 = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/sbcs-codec.js +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/sbcs-codec.js var require_sbcs_codec = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/sbcs-codec.js"(exports) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/sbcs-codec.js"(exports) { "use strict"; var Buffer2 = require_safer().Buffer; exports._sbcs = SBCSCodec; @@ -7699,9 +7708,9 @@ var require_sbcs_codec = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/sbcs-data.js +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/sbcs-data.js var require_sbcs_data = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/sbcs-data.js"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/sbcs-data.js"(exports, module) { "use strict"; module.exports = { // Not supported by iconv, not sure why. @@ -7852,9 +7861,9 @@ var require_sbcs_data = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/sbcs-data-generated.js +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/sbcs-data-generated.js var require_sbcs_data_generated = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/sbcs-data-generated.js"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/sbcs-data-generated.js"(exports, module) { "use strict"; module.exports = { "437": "cp437", @@ -8307,9 +8316,9 @@ var require_sbcs_data_generated = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/dbcs-codec.js +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/dbcs-codec.js var require_dbcs_codec = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/dbcs-codec.js"(exports) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/dbcs-codec.js"(exports) { "use strict"; var Buffer2 = require_safer().Buffer; exports._dbcs = DBCSCodec; @@ -8728,9 +8737,9 @@ var require_dbcs_codec = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/tables/shiftjis.json +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/shiftjis.json var require_shiftjis = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/tables/shiftjis.json"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/shiftjis.json"(exports, module) { module.exports = [ ["0", "\0", 128], ["a1", "\uFF61", 62], @@ -8859,9 +8868,9 @@ var require_shiftjis = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/tables/eucjp.json +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/eucjp.json var require_eucjp = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/tables/eucjp.json"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/eucjp.json"(exports, module) { module.exports = [ ["0", "\0", 127], ["8ea1", "\uFF61", 62], @@ -9047,9 +9056,9 @@ var require_eucjp = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/tables/cp936.json +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/cp936.json var require_cp936 = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/tables/cp936.json"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/cp936.json"(exports, module) { module.exports = [ ["0", "\0", 127, "\u20AC"], ["8140", "\u4E02\u4E04\u4E05\u4E06\u4E0F\u4E12\u4E17\u4E1F\u4E20\u4E21\u4E23\u4E26\u4E29\u4E2E\u4E2F\u4E31\u4E33\u4E35\u4E37\u4E3C\u4E40\u4E41\u4E42\u4E44\u4E46\u4E4A\u4E51\u4E55\u4E57\u4E5A\u4E5B\u4E62\u4E63\u4E64\u4E65\u4E67\u4E68\u4E6A", 5, "\u4E72\u4E74", 9, "\u4E7F", 6, "\u4E87\u4E8A"], @@ -9317,9 +9326,9 @@ var require_cp936 = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/tables/gbk-added.json +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/gbk-added.json var require_gbk_added = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/tables/gbk-added.json"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/gbk-added.json"(exports, module) { module.exports = [ ["a140", "\uE4C6", 62], ["a180", "\uE505", 32], @@ -9379,16 +9388,16 @@ var require_gbk_added = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/tables/gb18030-ranges.json +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/gb18030-ranges.json var require_gb18030_ranges = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/tables/gb18030-ranges.json"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/gb18030-ranges.json"(exports, module) { module.exports = { uChars: [128, 165, 169, 178, 184, 216, 226, 235, 238, 244, 248, 251, 253, 258, 276, 284, 300, 325, 329, 334, 364, 463, 465, 467, 469, 471, 473, 475, 477, 506, 594, 610, 712, 716, 730, 930, 938, 962, 970, 1026, 1104, 1106, 8209, 8215, 8218, 8222, 8231, 8241, 8244, 8246, 8252, 8365, 8452, 8454, 8458, 8471, 8482, 8556, 8570, 8596, 8602, 8713, 8720, 8722, 8726, 8731, 8737, 8740, 8742, 8748, 8751, 8760, 8766, 8777, 8781, 8787, 8802, 8808, 8816, 8854, 8858, 8870, 8896, 8979, 9322, 9372, 9548, 9588, 9616, 9622, 9634, 9652, 9662, 9672, 9676, 9680, 9702, 9735, 9738, 9793, 9795, 11906, 11909, 11913, 11917, 11928, 11944, 11947, 11951, 11956, 11960, 11964, 11979, 12284, 12292, 12312, 12319, 12330, 12351, 12436, 12447, 12535, 12543, 12586, 12842, 12850, 12964, 13200, 13215, 13218, 13253, 13263, 13267, 13270, 13384, 13428, 13727, 13839, 13851, 14617, 14703, 14801, 14816, 14964, 15183, 15471, 15585, 16471, 16736, 17208, 17325, 17330, 17374, 17623, 17997, 18018, 18212, 18218, 18301, 18318, 18760, 18811, 18814, 18820, 18823, 18844, 18848, 18872, 19576, 19620, 19738, 19887, 40870, 59244, 59336, 59367, 59413, 59417, 59423, 59431, 59437, 59443, 59452, 59460, 59478, 59493, 63789, 63866, 63894, 63976, 63986, 64016, 64018, 64021, 64025, 64034, 64037, 64042, 65074, 65093, 65107, 65112, 65127, 65132, 65375, 65510, 65536], gbChars: [0, 36, 38, 45, 50, 81, 89, 95, 96, 100, 103, 104, 105, 109, 126, 133, 148, 172, 175, 179, 208, 306, 307, 308, 309, 310, 311, 312, 313, 341, 428, 443, 544, 545, 558, 741, 742, 749, 750, 805, 819, 820, 7922, 7924, 7925, 7927, 7934, 7943, 7944, 7945, 7950, 8062, 8148, 8149, 8152, 8164, 8174, 8236, 8240, 8262, 8264, 8374, 8380, 8381, 8384, 8388, 8390, 8392, 8393, 8394, 8396, 8401, 8406, 8416, 8419, 8424, 8437, 8439, 8445, 8482, 8485, 8496, 8521, 8603, 8936, 8946, 9046, 9050, 9063, 9066, 9076, 9092, 9100, 9108, 9111, 9113, 9131, 9162, 9164, 9218, 9219, 11329, 11331, 11334, 11336, 11346, 11361, 11363, 11366, 11370, 11372, 11375, 11389, 11682, 11686, 11687, 11692, 11694, 11714, 11716, 11723, 11725, 11730, 11736, 11982, 11989, 12102, 12336, 12348, 12350, 12384, 12393, 12395, 12397, 12510, 12553, 12851, 12962, 12973, 13738, 13823, 13919, 13933, 14080, 14298, 14585, 14698, 15583, 15847, 16318, 16434, 16438, 16481, 16729, 17102, 17122, 17315, 17320, 17402, 17418, 17859, 17909, 17911, 17915, 17916, 17936, 17939, 17961, 18664, 18703, 18814, 18962, 19043, 33469, 33470, 33471, 33484, 33485, 33490, 33497, 33501, 33505, 33513, 33520, 33536, 33550, 37845, 37921, 37948, 38029, 38038, 38064, 38065, 38066, 38069, 38075, 38076, 38078, 39108, 39109, 39113, 39114, 39115, 39116, 39265, 39394, 189e3] }; } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/tables/cp949.json +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/cp949.json var require_cp949 = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/tables/cp949.json"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/cp949.json"(exports, module) { module.exports = [ ["0", "\0", 127], ["8141", "\uAC02\uAC03\uAC05\uAC06\uAC0B", 4, "\uAC18\uAC1E\uAC1F\uAC21\uAC22\uAC23\uAC25", 6, "\uAC2E\uAC32\uAC33\uAC34"], @@ -9665,9 +9674,9 @@ var require_cp949 = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/tables/cp950.json +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/cp950.json var require_cp950 = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/tables/cp950.json"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/cp950.json"(exports, module) { module.exports = [ ["0", "\0", 127], ["a140", "\u3000\uFF0C\u3001\u3002\uFF0E\u2027\uFF1B\uFF1A\uFF1F\uFF01\uFE30\u2026\u2025\uFE50\uFE51\uFE52\xB7\uFE54\uFE55\uFE56\uFE57\uFF5C\u2013\uFE31\u2014\uFE33\u2574\uFE34\uFE4F\uFF08\uFF09\uFE35\uFE36\uFF5B\uFF5D\uFE37\uFE38\u3014\u3015\uFE39\uFE3A\u3010\u3011\uFE3B\uFE3C\u300A\u300B\uFE3D\uFE3E\u3008\u3009\uFE3F\uFE40\u300C\u300D\uFE41\uFE42\u300E\u300F\uFE43\uFE44\uFE59\uFE5A"], @@ -9848,9 +9857,9 @@ var require_cp950 = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/tables/big5-added.json +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/big5-added.json var require_big5_added = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/tables/big5-added.json"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/tables/big5-added.json"(exports, module) { module.exports = [ ["8740", "\u43F0\u4C32\u4603\u45A6\u4578\u{27267}\u4D77\u45B3\u{27CB1}\u4CE2\u{27CC5}\u3B95\u4736\u4744\u4C47\u4C40\u{242BF}\u{23617}\u{27352}\u{26E8B}\u{270D2}\u4C57\u{2A351}\u474F\u45DA\u4C85\u{27C6C}\u4D07\u4AA4\u46A1\u{26B23}\u7225\u{25A54}\u{21A63}\u{23E06}\u{23F61}\u664D\u56FB"], ["8767", "\u7D95\u591D\u{28BB9}\u3DF4\u9734\u{27BEF}\u5BDB\u{21D5E}\u5AA4\u3625\u{29EB0}\u5AD1\u5BB7\u5CFC\u676E\u8593\u{29945}\u7461\u749D\u3875\u{21D53}\u{2369E}\u{26021}\u3EEC"], @@ -9976,9 +9985,9 @@ var require_big5_added = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/dbcs-data.js +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/dbcs-data.js var require_dbcs_data = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/dbcs-data.js"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/dbcs-data.js"(exports, module) { "use strict"; module.exports = { // == Japanese/ShiftJIS ==================================================== @@ -10223,9 +10232,9 @@ var require_dbcs_data = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/encodings/index.js +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/index.js var require_encodings = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/encodings/index.js"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/encodings/index.js"(exports, module) { "use strict"; var modules = [ require_internal(), @@ -10250,9 +10259,9 @@ var require_encodings = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/lib/streams.js +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/lib/streams.js var require_streams = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/lib/streams.js"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/lib/streams.js"(exports, module) { "use strict"; var Buffer2 = require_safer().Buffer; module.exports = function(stream_module) { @@ -10349,9 +10358,9 @@ var require_streams = __commonJS({ } }); -// ../node_modules/libmime/node_modules/iconv-lite/lib/index.js +// ../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/lib/index.js var require_lib = __commonJS({ - "../node_modules/libmime/node_modules/iconv-lite/lib/index.js"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/iconv-lite/lib/index.js"(exports, module) { "use strict"; var Buffer2 = require_safer().Buffer; var bomHandling = require_bom_handling(); @@ -26905,9 +26914,9 @@ var require_src = __commonJS({ } }); -// ../node_modules/libmime/lib/charsets.js +// ../node_modules/@zone-eu/mailsplit/node_modules/libmime/lib/charsets.js var require_charsets = __commonJS({ - "../node_modules/libmime/lib/charsets.js"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/libmime/lib/charsets.js"(exports, module) { "use strict"; module.exports = { "866": "IBM866", @@ -27120,9 +27129,9 @@ var require_charsets = __commonJS({ } }); -// ../node_modules/libmime/lib/charset.js +// ../node_modules/@zone-eu/mailsplit/node_modules/libmime/lib/charset.js var require_charset = __commonJS({ - "../node_modules/libmime/lib/charset.js"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/libmime/lib/charset.js"(exports, module) { "use strict"; var { Buffer: Buffer2 } = __require("node:buffer"); var iconv = require_lib(); @@ -27599,9 +27608,9 @@ var require_libqp = __commonJS({ } }); -// ../node_modules/libmime/lib/mimetypes.js +// ../node_modules/@zone-eu/mailsplit/node_modules/libmime/lib/mimetypes.js var require_mimetypes = __commonJS({ - "../node_modules/libmime/lib/mimetypes.js"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/libmime/lib/mimetypes.js"(exports, module) { "use strict"; module.exports = { list: { @@ -29650,9 +29659,9 @@ var require_mimetypes = __commonJS({ } }); -// ../node_modules/libmime/lib/libmime.js +// ../node_modules/@zone-eu/mailsplit/node_modules/libmime/lib/libmime.js var require_libmime = __commonJS({ - "../node_modules/libmime/lib/libmime.js"(exports, module) { + "../node_modules/@zone-eu/mailsplit/node_modules/libmime/lib/libmime.js"(exports, module) { "use strict"; var { Buffer: Buffer2 } = __require("node:buffer"); var libcharset = require_charset(); @@ -31474,797 +31483,244 @@ var require_mailsplit = __commonJS({ } }); -// ../node_modules/nodemailer/lib/addressparser/index.js -var require_addressparser = __commonJS({ - "../node_modules/nodemailer/lib/addressparser/index.js"(exports, module) { +// ../node_modules/iconv-lite/lib/bom-handling.js +var require_bom_handling2 = __commonJS({ + "../node_modules/iconv-lite/lib/bom-handling.js"(exports) { "use strict"; - function _handleAddress(tokens, depth) { - let isGroup = false; - let state = "text"; - let address; - let addresses = []; - let data = { - address: [], - comment: [], - group: [], - text: [], - textWasQuoted: [] - // Track which text tokens came from inside quotes - }; - let i; - let len; - let insideQuotes = false; - for (i = 0, len = tokens.length; i < len; i++) { - let token = tokens[i]; - let prevToken = i ? tokens[i - 1] : null; - if (token.type === "operator") { - switch (token.value) { - case "<": - state = "address"; - insideQuotes = false; - break; - case "(": - state = "comment"; - insideQuotes = false; - break; - case ":": - state = "group"; - isGroup = true; - insideQuotes = false; - break; - case '"': - insideQuotes = !insideQuotes; - state = "text"; - break; - default: - state = "text"; - insideQuotes = false; - break; - } - } else if (token.value) { - if (state === "address") { - token.value = token.value.replace(/^[^<]*<\s*/, ""); - } - if (prevToken && prevToken.noBreak && data[state].length) { - data[state][data[state].length - 1] += token.value; - if (state === "text" && insideQuotes) { - data.textWasQuoted[data.textWasQuoted.length - 1] = true; - } - } else { - data[state].push(token.value); - if (state === "text") { - data.textWasQuoted.push(insideQuotes); - } - } - } - } - if (!data.text.length && data.comment.length) { - data.text = data.comment; - data.comment = []; - } - if (isGroup) { - data.text = data.text.join(" "); - let groupMembers = []; - if (data.group.length) { - let parsedGroup = addressparser(data.group.join(","), { _depth: depth + 1 }); - parsedGroup.forEach((member) => { - if (member.group) { - groupMembers = groupMembers.concat(member.group); - } else { - groupMembers.push(member); - } - }); - } - addresses.push({ - name: data.text || address && address.name, - group: groupMembers - }); - } else { - if (!data.address.length && data.text.length) { - for (i = data.text.length - 1; i >= 0; i--) { - if (!data.textWasQuoted[i] && data.text[i].match(/^[^@\s]+@[^@\s]+$/)) { - data.address = data.text.splice(i, 1); - data.textWasQuoted.splice(i, 1); - break; - } - } - let _regexHandler = function(address2) { - if (!data.address.length) { - data.address = [address2.trim()]; - return " "; - } else { - return address2; - } - }; - if (!data.address.length) { - for (i = data.text.length - 1; i >= 0; i--) { - if (!data.textWasQuoted[i]) { - data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, _regexHandler).trim(); - if (data.address.length) { - break; - } - } - } - } - } - if (!data.text.length && data.comment.length) { - data.text = data.comment; - data.comment = []; - } - if (data.address.length > 1) { - data.text = data.text.concat(data.address.splice(1)); - } - data.text = data.text.join(" "); - data.address = data.address.join(" "); - if (!data.address && isGroup) { - return []; - } else { - address = { - address: data.address || data.text || "", - name: data.text || data.address || "" - }; - if (address.address === address.name) { - if ((address.address || "").match(/@/)) { - address.name = ""; - } else { - address.address = ""; - } - } - addresses.push(address); - } - } - return addresses; + var BOMChar = "\uFEFF"; + exports.PrependBOM = PrependBOMWrapper; + function PrependBOMWrapper(encoder, options) { + this.encoder = encoder; + this.addBOM = true; } - var Tokenizer = class { - constructor(str) { - this.str = (str || "").toString(); - this.operatorCurrent = ""; - this.operatorExpecting = ""; - this.node = null; - this.escaped = false; - this.list = []; - this.operators = { - '"': '"', - "(": ")", - "<": ">", - ",": "", - ":": ";", - // Semicolons are not a legal delimiter per the RFC2822 grammar other - // than for terminating a group, but they are also not valid for any - // other use in this context. Given that some mail clients have - // historically allowed the semicolon as a delimiter equivalent to the - // comma in their UI, it makes sense to treat them the same as a comma - // when used outside of a group. - ";": "" - }; - } - /** - * Tokenizes the original input string - * - * @return {Array} An array of operator|text tokens - */ - tokenize() { - let list = []; - for (let i = 0, len = this.str.length; i < len; i++) { - let chr = this.str.charAt(i); - let nextChr = i < len - 1 ? this.str.charAt(i + 1) : null; - this.checkChar(chr, nextChr); - } - this.list.forEach((node) => { - node.value = (node.value || "").toString().trim(); - if (node.value) { - list.push(node); - } - }); - return list; - } - /** - * Checks if a character is an operator or text and acts accordingly - * - * @param {String} chr Character from the address field - */ - checkChar(chr, nextChr) { - if (this.escaped) { - } else if (chr === this.operatorExpecting) { - this.node = { - type: "operator", - value: chr - }; - if (nextChr && ![" ", " ", "\r", "\n", ",", ";"].includes(nextChr)) { - this.node.noBreak = true; - } - this.list.push(this.node); - this.node = null; - this.operatorExpecting = ""; - this.escaped = false; - return; - } else if (!this.operatorExpecting && chr in this.operators) { - this.node = { - type: "operator", - value: chr - }; - this.list.push(this.node); - this.node = null; - this.operatorExpecting = this.operators[chr]; - this.escaped = false; - return; - } else if (['"', "'"].includes(this.operatorExpecting) && chr === "\\") { - this.escaped = true; - return; - } - if (!this.node) { - this.node = { - type: "text", - value: "" - }; - this.list.push(this.node); - } - if (chr === "\n") { - chr = " "; - } - if (chr.charCodeAt(0) >= 33 || [" ", " "].includes(chr)) { - this.node.value += chr; - } - this.escaped = false; + PrependBOMWrapper.prototype.write = function(str) { + if (this.addBOM) { + str = BOMChar + str; + this.addBOM = false; } + return this.encoder.write(str); }; - var MAX_NESTED_GROUP_DEPTH = 50; - function addressparser(str, options) { - options = options || {}; - let depth = options._depth || 0; - if (depth > MAX_NESTED_GROUP_DEPTH) { - return []; + PrependBOMWrapper.prototype.end = function() { + return this.encoder.end(); + }; + exports.StripBOM = StripBOMWrapper; + function StripBOMWrapper(decoder, options) { + this.decoder = decoder; + this.pass = false; + this.options = options || {}; + } + StripBOMWrapper.prototype.write = function(buf) { + var res = this.decoder.write(buf); + if (this.pass || !res) { + return res; } - let tokenizer = new Tokenizer(str); - let tokens = tokenizer.tokenize(); - let addresses = []; - let address = []; - let parsedAddresses = []; - tokens.forEach((token) => { - if (token.type === "operator" && (token.value === "," || token.value === ";")) { - if (address.length) { - addresses.push(address); - } - address = []; - } else { - address.push(token); + if (res[0] === BOMChar) { + res = res.slice(1); + if (typeof this.options.stripBOM === "function") { + this.options.stripBOM(); } - }); - if (address.length) { - addresses.push(address); } - addresses.forEach((address2) => { - address2 = _handleAddress(address2, depth); - if (address2.length) { - parsedAddresses = parsedAddresses.concat(address2); + this.pass = true; + return res; + }; + StripBOMWrapper.prototype.end = function() { + return this.decoder.end(); + }; + } +}); + +// ../node_modules/iconv-lite/lib/helpers/merge-exports.js +var require_merge_exports = __commonJS({ + "../node_modules/iconv-lite/lib/helpers/merge-exports.js"(exports, module) { + "use strict"; + var hasOwn = typeof Object.hasOwn === "undefined" ? Function.call.bind(Object.prototype.hasOwnProperty) : Object.hasOwn; + function mergeModules(target, module2) { + for (var key in module2) { + if (hasOwn(module2, key)) { + target[key] = module2[key]; } - }); - if (options.flatten) { - let addresses2 = []; - let walkAddressList = (list) => { - list.forEach((address2) => { - if (address2.group) { - return walkAddressList(address2.group); - } else { - addresses2.push(address2); - } - }); - }; - walkAddressList(parsedAddresses); - return addresses2; } - return parsedAddresses; } - module.exports = addressparser; + module.exports = mergeModules; } }); -// ../node_modules/punycode.js/punycode.js -var require_punycode = __commonJS({ - "../node_modules/punycode.js/punycode.js"(exports, module) { +// ../node_modules/iconv-lite/encodings/internal.js +var require_internal2 = __commonJS({ + "../node_modules/iconv-lite/encodings/internal.js"(exports, module) { "use strict"; - var maxInt = 2147483647; - var base = 36; - var tMin = 1; - var tMax = 26; - var skew = 38; - var damp = 700; - var initialBias = 72; - var initialN = 128; - var delimiter = "-"; - var regexPunycode = /^xn--/; - var regexNonASCII = /[^\0-\x7F]/; - var regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g; - var errors = { - "overflow": "Overflow: input needs wider integers to process", - "not-basic": "Illegal input >= 0x80 (not a basic code point)", - "invalid-input": "Invalid input" + var Buffer2 = require_safer().Buffer; + module.exports = { + // Encodings + utf8: { type: "_internal", bomAware: true }, + cesu8: { type: "_internal", bomAware: true }, + unicode11utf8: "utf8", + ucs2: { type: "_internal", bomAware: true }, + utf16le: "ucs2", + binary: { type: "_internal" }, + base64: { type: "_internal" }, + hex: { type: "_internal" }, + // Codec. + _internal: InternalCodec }; - var baseMinusTMin = base - tMin; - var floor = Math.floor; - var stringFromCharCode = String.fromCharCode; - function error2(type) { - throw new RangeError(errors[type]); + function InternalCodec(codecOptions, iconv) { + this.enc = codecOptions.encodingName; + this.bomAware = codecOptions.bomAware; + if (this.enc === "base64") { + this.encoder = InternalEncoderBase64; + } else if (this.enc === "utf8") { + this.encoder = InternalEncoderUtf8; + } else if (this.enc === "cesu8") { + this.enc = "utf8"; + this.encoder = InternalEncoderCesu8; + if (Buffer2.from("eda0bdedb2a9", "hex").toString() !== "\u{1F4A9}") { + this.decoder = InternalDecoderCesu8; + this.defaultCharUnicode = iconv.defaultCharUnicode; + } + } } - function map2(array2, callback) { - const result = []; - let length = array2.length; - while (length--) { - result[length] = callback(array2[length]); + InternalCodec.prototype.encoder = InternalEncoder; + InternalCodec.prototype.decoder = InternalDecoder; + var StringDecoder = __require("string_decoder").StringDecoder; + function InternalDecoder(options, codec2) { + this.decoder = new StringDecoder(codec2.enc); + } + InternalDecoder.prototype.write = function(buf) { + if (!Buffer2.isBuffer(buf)) { + buf = Buffer2.from(buf); } - return result; + return this.decoder.write(buf); + }; + InternalDecoder.prototype.end = function() { + return this.decoder.end(); + }; + function InternalEncoder(options, codec2) { + this.enc = codec2.enc; } - function mapDomain(domain2, callback) { - const parts = domain2.split("@"); - let result = ""; - if (parts.length > 1) { - result = parts[0] + "@"; - domain2 = parts[1]; + InternalEncoder.prototype.write = function(str) { + return Buffer2.from(str, this.enc); + }; + InternalEncoder.prototype.end = function() { + }; + function InternalEncoderBase64(options, codec2) { + this.prevStr = ""; + } + InternalEncoderBase64.prototype.write = function(str) { + str = this.prevStr + str; + var completeQuads = str.length - str.length % 4; + this.prevStr = str.slice(completeQuads); + str = str.slice(0, completeQuads); + return Buffer2.from(str, "base64"); + }; + InternalEncoderBase64.prototype.end = function() { + return Buffer2.from(this.prevStr, "base64"); + }; + function InternalEncoderCesu8(options, codec2) { + } + InternalEncoderCesu8.prototype.write = function(str) { + var buf = Buffer2.alloc(str.length * 3); + var bufIdx = 0; + for (var i = 0; i < str.length; i++) { + var charCode = str.charCodeAt(i); + if (charCode < 128) { + buf[bufIdx++] = charCode; + } else if (charCode < 2048) { + buf[bufIdx++] = 192 + (charCode >>> 6); + buf[bufIdx++] = 128 + (charCode & 63); + } else { + buf[bufIdx++] = 224 + (charCode >>> 12); + buf[bufIdx++] = 128 + (charCode >>> 6 & 63); + buf[bufIdx++] = 128 + (charCode & 63); + } } - domain2 = domain2.replace(regexSeparators, "."); - const labels = domain2.split("."); - const encoded = map2(labels, callback).join("."); - return result + encoded; + return buf.slice(0, bufIdx); + }; + InternalEncoderCesu8.prototype.end = function() { + }; + function InternalDecoderCesu8(options, codec2) { + this.acc = 0; + this.contBytes = 0; + this.accBytes = 0; + this.defaultCharUnicode = codec2.defaultCharUnicode; } - function ucs2decode(string3) { - const output = []; - let counter = 0; - const length = string3.length; - while (counter < length) { - const value = string3.charCodeAt(counter++); - if (value >= 55296 && value <= 56319 && counter < length) { - const extra = string3.charCodeAt(counter++); - if ((extra & 64512) == 56320) { - output.push(((value & 1023) << 10) + (extra & 1023) + 65536); + InternalDecoderCesu8.prototype.write = function(buf) { + var acc = this.acc; + var contBytes = this.contBytes; + var accBytes = this.accBytes; + var res = ""; + for (var i = 0; i < buf.length; i++) { + var curByte = buf[i]; + if ((curByte & 192) !== 128) { + if (contBytes > 0) { + res += this.defaultCharUnicode; + contBytes = 0; + } + if (curByte < 128) { + res += String.fromCharCode(curByte); + } else if (curByte < 224) { + acc = curByte & 31; + contBytes = 1; + accBytes = 1; + } else if (curByte < 240) { + acc = curByte & 15; + contBytes = 2; + accBytes = 1; } else { - output.push(value); - counter--; + res += this.defaultCharUnicode; } } else { - output.push(value); + if (contBytes > 0) { + acc = acc << 6 | curByte & 63; + contBytes--; + accBytes++; + if (contBytes === 0) { + if (accBytes === 2 && acc < 128 && acc > 0) { + res += this.defaultCharUnicode; + } else if (accBytes === 3 && acc < 2048) { + res += this.defaultCharUnicode; + } else { + res += String.fromCharCode(acc); + } + } + } else { + res += this.defaultCharUnicode; + } } } - return output; + this.acc = acc; + this.contBytes = contBytes; + this.accBytes = accBytes; + return res; + }; + InternalDecoderCesu8.prototype.end = function() { + var res = 0; + if (this.contBytes > 0) { + res += this.defaultCharUnicode; + } + return res; + }; + function InternalEncoderUtf8(options, codec2) { + this.highSurrogate = ""; } - var ucs2encode = (codePoints) => String.fromCodePoint(...codePoints); - var basicToDigit = function(codePoint) { - if (codePoint >= 48 && codePoint < 58) { - return 26 + (codePoint - 48); + InternalEncoderUtf8.prototype.write = function(str) { + if (this.highSurrogate) { + str = this.highSurrogate + str; + this.highSurrogate = ""; } - if (codePoint >= 65 && codePoint < 91) { - return codePoint - 65; + if (str.length > 0) { + var charCode = str.charCodeAt(str.length - 1); + if (charCode >= 55296 && charCode < 56320) { + this.highSurrogate = str[str.length - 1]; + str = str.slice(0, str.length - 1); + } } - if (codePoint >= 97 && codePoint < 123) { - return codePoint - 97; - } - return base; - }; - var digitToBasic = function(digit, flag) { - return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); - }; - var adapt = function(delta, numPoints, firstTime) { - let k = 0; - delta = firstTime ? floor(delta / damp) : delta >> 1; - delta += floor(delta / numPoints); - for (; delta > baseMinusTMin * tMax >> 1; k += base) { - delta = floor(delta / baseMinusTMin); - } - return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); - }; - var decode3 = function(input) { - const output = []; - const inputLength = input.length; - let i = 0; - let n = initialN; - let bias = initialBias; - let basic = input.lastIndexOf(delimiter); - if (basic < 0) { - basic = 0; - } - for (let j = 0; j < basic; ++j) { - if (input.charCodeAt(j) >= 128) { - error2("not-basic"); - } - output.push(input.charCodeAt(j)); - } - for (let index = basic > 0 ? basic + 1 : 0; index < inputLength; ) { - const oldi = i; - for (let w = 1, k = base; ; k += base) { - if (index >= inputLength) { - error2("invalid-input"); - } - const digit = basicToDigit(input.charCodeAt(index++)); - if (digit >= base) { - error2("invalid-input"); - } - if (digit > floor((maxInt - i) / w)) { - error2("overflow"); - } - i += digit * w; - const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias; - if (digit < t) { - break; - } - const baseMinusT = base - t; - if (w > floor(maxInt / baseMinusT)) { - error2("overflow"); - } - w *= baseMinusT; - } - const out = output.length + 1; - bias = adapt(i - oldi, out, oldi == 0); - if (floor(i / out) > maxInt - n) { - error2("overflow"); - } - n += floor(i / out); - i %= out; - output.splice(i++, 0, n); - } - return String.fromCodePoint(...output); - }; - var encode3 = function(input) { - const output = []; - input = ucs2decode(input); - const inputLength = input.length; - let n = initialN; - let delta = 0; - let bias = initialBias; - for (const currentValue of input) { - if (currentValue < 128) { - output.push(stringFromCharCode(currentValue)); - } - } - const basicLength = output.length; - let handledCPCount = basicLength; - if (basicLength) { - output.push(delimiter); - } - while (handledCPCount < inputLength) { - let m = maxInt; - for (const currentValue of input) { - if (currentValue >= n && currentValue < m) { - m = currentValue; - } - } - const handledCPCountPlusOne = handledCPCount + 1; - if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { - error2("overflow"); - } - delta += (m - n) * handledCPCountPlusOne; - n = m; - for (const currentValue of input) { - if (currentValue < n && ++delta > maxInt) { - error2("overflow"); - } - if (currentValue === n) { - let q = delta; - for (let k = base; ; k += base) { - const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias; - if (q < t) { - break; - } - const qMinusT = q - t; - const baseMinusT = base - t; - output.push( - stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) - ); - q = floor(qMinusT / baseMinusT); - } - output.push(stringFromCharCode(digitToBasic(q, 0))); - bias = adapt(delta, handledCPCountPlusOne, handledCPCount === basicLength); - delta = 0; - ++handledCPCount; - } - } - ++delta; - ++n; - } - return output.join(""); - }; - var toUnicode = function(input) { - return mapDomain(input, function(string3) { - return regexPunycode.test(string3) ? decode3(string3.slice(4).toLowerCase()) : string3; - }); - }; - var toASCII = function(input) { - return mapDomain(input, function(string3) { - return regexNonASCII.test(string3) ? "xn--" + encode3(string3) : string3; - }); - }; - var punycode = { - /** - * A string representing the current Punycode.js version number. - * @memberOf punycode - * @type String - */ - "version": "2.3.1", - /** - * An object of methods to convert from JavaScript's internal character - * representation (UCS-2) to Unicode code points, and back. - * @see - * @memberOf punycode - * @type Object - */ - "ucs2": { - "decode": ucs2decode, - "encode": ucs2encode - }, - "decode": decode3, - "encode": encode3, - "toASCII": toASCII, - "toUnicode": toUnicode - }; - module.exports = punycode; - } -}); - -// ../node_modules/mailparser/lib/stream-hash.js -var require_stream_hash = __commonJS({ - "../node_modules/mailparser/lib/stream-hash.js"(exports, module) { - "use strict"; - var crypto = __require("crypto"); - var Transform = __require("stream").Transform; - var StreamHash = class extends Transform { - constructor(attachment, algo) { - super(); - this.attachment = attachment; - this.algo = (algo || "md5").toLowerCase(); - this.hash = crypto.createHash(algo); - this.byteCount = 0; - } - _transform(chunk, encoding, done) { - this.hash.update(chunk); - this.byteCount += chunk.length; - done(null, chunk); - } - _flush(done) { - this.attachment.checksum = this.hash.digest("hex"); - this.attachment.size = this.byteCount; - done(); - } - }; - module.exports = StreamHash; - } -}); - -// ../node_modules/iconv-lite/lib/bom-handling.js -var require_bom_handling2 = __commonJS({ - "../node_modules/iconv-lite/lib/bom-handling.js"(exports) { - "use strict"; - var BOMChar = "\uFEFF"; - exports.PrependBOM = PrependBOMWrapper; - function PrependBOMWrapper(encoder, options) { - this.encoder = encoder; - this.addBOM = true; - } - PrependBOMWrapper.prototype.write = function(str) { - if (this.addBOM) { - str = BOMChar + str; - this.addBOM = false; - } - return this.encoder.write(str); - }; - PrependBOMWrapper.prototype.end = function() { - return this.encoder.end(); - }; - exports.StripBOM = StripBOMWrapper; - function StripBOMWrapper(decoder, options) { - this.decoder = decoder; - this.pass = false; - this.options = options || {}; - } - StripBOMWrapper.prototype.write = function(buf) { - var res = this.decoder.write(buf); - if (this.pass || !res) { - return res; - } - if (res[0] === BOMChar) { - res = res.slice(1); - if (typeof this.options.stripBOM === "function") { - this.options.stripBOM(); - } - } - this.pass = true; - return res; - }; - StripBOMWrapper.prototype.end = function() { - return this.decoder.end(); - }; - } -}); - -// ../node_modules/iconv-lite/lib/helpers/merge-exports.js -var require_merge_exports = __commonJS({ - "../node_modules/iconv-lite/lib/helpers/merge-exports.js"(exports, module) { - "use strict"; - var hasOwn = typeof Object.hasOwn === "undefined" ? Function.call.bind(Object.prototype.hasOwnProperty) : Object.hasOwn; - function mergeModules(target, module2) { - for (var key in module2) { - if (hasOwn(module2, key)) { - target[key] = module2[key]; - } - } - } - module.exports = mergeModules; - } -}); - -// ../node_modules/iconv-lite/encodings/internal.js -var require_internal2 = __commonJS({ - "../node_modules/iconv-lite/encodings/internal.js"(exports, module) { - "use strict"; - var Buffer2 = require_safer().Buffer; - module.exports = { - // Encodings - utf8: { type: "_internal", bomAware: true }, - cesu8: { type: "_internal", bomAware: true }, - unicode11utf8: "utf8", - ucs2: { type: "_internal", bomAware: true }, - utf16le: "ucs2", - binary: { type: "_internal" }, - base64: { type: "_internal" }, - hex: { type: "_internal" }, - // Codec. - _internal: InternalCodec - }; - function InternalCodec(codecOptions, iconv) { - this.enc = codecOptions.encodingName; - this.bomAware = codecOptions.bomAware; - if (this.enc === "base64") { - this.encoder = InternalEncoderBase64; - } else if (this.enc === "utf8") { - this.encoder = InternalEncoderUtf8; - } else if (this.enc === "cesu8") { - this.enc = "utf8"; - this.encoder = InternalEncoderCesu8; - if (Buffer2.from("eda0bdedb2a9", "hex").toString() !== "\u{1F4A9}") { - this.decoder = InternalDecoderCesu8; - this.defaultCharUnicode = iconv.defaultCharUnicode; - } - } - } - InternalCodec.prototype.encoder = InternalEncoder; - InternalCodec.prototype.decoder = InternalDecoder; - var StringDecoder = __require("string_decoder").StringDecoder; - function InternalDecoder(options, codec2) { - this.decoder = new StringDecoder(codec2.enc); - } - InternalDecoder.prototype.write = function(buf) { - if (!Buffer2.isBuffer(buf)) { - buf = Buffer2.from(buf); - } - return this.decoder.write(buf); - }; - InternalDecoder.prototype.end = function() { - return this.decoder.end(); - }; - function InternalEncoder(options, codec2) { - this.enc = codec2.enc; - } - InternalEncoder.prototype.write = function(str) { - return Buffer2.from(str, this.enc); - }; - InternalEncoder.prototype.end = function() { - }; - function InternalEncoderBase64(options, codec2) { - this.prevStr = ""; - } - InternalEncoderBase64.prototype.write = function(str) { - str = this.prevStr + str; - var completeQuads = str.length - str.length % 4; - this.prevStr = str.slice(completeQuads); - str = str.slice(0, completeQuads); - return Buffer2.from(str, "base64"); - }; - InternalEncoderBase64.prototype.end = function() { - return Buffer2.from(this.prevStr, "base64"); - }; - function InternalEncoderCesu8(options, codec2) { - } - InternalEncoderCesu8.prototype.write = function(str) { - var buf = Buffer2.alloc(str.length * 3); - var bufIdx = 0; - for (var i = 0; i < str.length; i++) { - var charCode = str.charCodeAt(i); - if (charCode < 128) { - buf[bufIdx++] = charCode; - } else if (charCode < 2048) { - buf[bufIdx++] = 192 + (charCode >>> 6); - buf[bufIdx++] = 128 + (charCode & 63); - } else { - buf[bufIdx++] = 224 + (charCode >>> 12); - buf[bufIdx++] = 128 + (charCode >>> 6 & 63); - buf[bufIdx++] = 128 + (charCode & 63); - } - } - return buf.slice(0, bufIdx); - }; - InternalEncoderCesu8.prototype.end = function() { - }; - function InternalDecoderCesu8(options, codec2) { - this.acc = 0; - this.contBytes = 0; - this.accBytes = 0; - this.defaultCharUnicode = codec2.defaultCharUnicode; - } - InternalDecoderCesu8.prototype.write = function(buf) { - var acc = this.acc; - var contBytes = this.contBytes; - var accBytes = this.accBytes; - var res = ""; - for (var i = 0; i < buf.length; i++) { - var curByte = buf[i]; - if ((curByte & 192) !== 128) { - if (contBytes > 0) { - res += this.defaultCharUnicode; - contBytes = 0; - } - if (curByte < 128) { - res += String.fromCharCode(curByte); - } else if (curByte < 224) { - acc = curByte & 31; - contBytes = 1; - accBytes = 1; - } else if (curByte < 240) { - acc = curByte & 15; - contBytes = 2; - accBytes = 1; - } else { - res += this.defaultCharUnicode; - } - } else { - if (contBytes > 0) { - acc = acc << 6 | curByte & 63; - contBytes--; - accBytes++; - if (contBytes === 0) { - if (accBytes === 2 && acc < 128 && acc > 0) { - res += this.defaultCharUnicode; - } else if (accBytes === 3 && acc < 2048) { - res += this.defaultCharUnicode; - } else { - res += String.fromCharCode(acc); - } - } - } else { - res += this.defaultCharUnicode; - } - } - } - this.acc = acc; - this.contBytes = contBytes; - this.accBytes = accBytes; - return res; - }; - InternalDecoderCesu8.prototype.end = function() { - var res = 0; - if (this.contBytes > 0) { - res += this.defaultCharUnicode; - } - return res; - }; - function InternalEncoderUtf8(options, codec2) { - this.highSurrogate = ""; - } - InternalEncoderUtf8.prototype.write = function(str) { - if (this.highSurrogate) { - str = this.highSurrogate + str; - this.highSurrogate = ""; - } - if (str.length > 0) { - var charCode = str.charCodeAt(str.length - 1); - if (charCode >= 55296 && charCode < 56320) { - this.highSurrogate = str[str.length - 1]; - str = str.slice(0, str.length - 1); - } - } - return Buffer2.from(str, this.enc); - }; - InternalEncoderUtf8.prototype.end = function() { - if (this.highSurrogate) { - var str = this.highSurrogate; - this.highSurrogate = ""; - return Buffer2.from(str, this.enc); + return Buffer2.from(str, this.enc); + }; + InternalEncoderUtf8.prototype.end = function() { + if (this.highSurrogate) { + var str = this.highSurrogate; + this.highSurrogate = ""; + return Buffer2.from(str, this.enc); } }; } @@ -35599,165 +35055,3789 @@ var require_streams2 = __commonJS({ } catch (e) { done(e); } - }; - IconvLiteDecoderStream.prototype._flush = function(done) { - try { - var res = this.conv.end(); - if (res && res.length) - this.push(res, this.encoding); - done(); - } catch (e) { - done(e); + }; + IconvLiteDecoderStream.prototype._flush = function(done) { + try { + var res = this.conv.end(); + if (res && res.length) + this.push(res, this.encoding); + done(); + } catch (e) { + done(e); + } + }; + IconvLiteDecoderStream.prototype.collect = function(cb) { + var res = ""; + this.on("error", cb); + this.on("data", function(chunk) { + res += chunk; + }); + this.on("end", function() { + cb(null, res); + }); + return this; + }; + return { + IconvLiteEncoderStream, + IconvLiteDecoderStream + }; + }; + } +}); + +// ../node_modules/iconv-lite/lib/index.js +var require_lib2 = __commonJS({ + "../node_modules/iconv-lite/lib/index.js"(exports, module) { + "use strict"; + var Buffer2 = require_safer().Buffer; + var bomHandling = require_bom_handling2(); + var mergeModules = require_merge_exports(); + module.exports.encodings = null; + module.exports.defaultCharUnicode = "\uFFFD"; + module.exports.defaultCharSingleByte = "?"; + module.exports.encode = function encode3(str, encoding, options) { + str = "" + (str || ""); + var encoder = module.exports.getEncoder(encoding, options); + var res = encoder.write(str); + var trail = encoder.end(); + return trail && trail.length > 0 ? Buffer2.concat([res, trail]) : res; + }; + module.exports.decode = function decode3(buf, encoding, options) { + if (typeof buf === "string") { + if (!module.exports.skipDecodeWarning) { + console.error("Iconv-lite warning: decode()-ing strings is deprecated. Refer to https://github.com/ashtuchkin/iconv-lite/wiki/Use-Buffers-when-decoding"); + module.exports.skipDecodeWarning = true; + } + buf = Buffer2.from("" + (buf || ""), "binary"); + } + var decoder = module.exports.getDecoder(encoding, options); + var res = decoder.write(buf); + var trail = decoder.end(); + return trail ? res + trail : res; + }; + module.exports.encodingExists = function encodingExists(enc) { + try { + module.exports.getCodec(enc); + return true; + } catch (e) { + return false; + } + }; + module.exports.toEncoding = module.exports.encode; + module.exports.fromEncoding = module.exports.decode; + module.exports._codecDataCache = { __proto__: null }; + module.exports.getCodec = function getCodec(encoding) { + if (!module.exports.encodings) { + var raw = require_encodings2(); + module.exports.encodings = { __proto__: null }; + mergeModules(module.exports.encodings, raw); + } + var enc = module.exports._canonicalizeEncoding(encoding); + var codecOptions = {}; + while (true) { + var codec2 = module.exports._codecDataCache[enc]; + if (codec2) { + return codec2; + } + var codecDef = module.exports.encodings[enc]; + switch (typeof codecDef) { + case "string": + enc = codecDef; + break; + case "object": + for (var key in codecDef) { + codecOptions[key] = codecDef[key]; + } + if (!codecOptions.encodingName) { + codecOptions.encodingName = enc; + } + enc = codecDef.type; + break; + case "function": + if (!codecOptions.encodingName) { + codecOptions.encodingName = enc; + } + codec2 = new codecDef(codecOptions, module.exports); + module.exports._codecDataCache[codecOptions.encodingName] = codec2; + return codec2; + default: + throw new Error("Encoding not recognized: '" + encoding + "' (searched as: '" + enc + "')"); + } + } + }; + module.exports._canonicalizeEncoding = function(encoding) { + return ("" + encoding).toLowerCase().replace(/:\d{4}$|[^0-9a-z]/g, ""); + }; + module.exports.getEncoder = function getEncoder(encoding, options) { + var codec2 = module.exports.getCodec(encoding); + var encoder = new codec2.encoder(options, codec2); + if (codec2.bomAware && options && options.addBOM) { + encoder = new bomHandling.PrependBOM(encoder, options); + } + return encoder; + }; + module.exports.getDecoder = function getDecoder(encoding, options) { + var codec2 = module.exports.getCodec(encoding); + var decoder = new codec2.decoder(options, codec2); + if (codec2.bomAware && !(options && options.stripBOM === false)) { + decoder = new bomHandling.StripBOM(decoder, options); + } + return decoder; + }; + module.exports.enableStreamingAPI = function enableStreamingAPI(streamModule2) { + if (module.exports.supportsStreams) { + return; + } + var streams = require_streams2()(streamModule2); + module.exports.IconvLiteEncoderStream = streams.IconvLiteEncoderStream; + module.exports.IconvLiteDecoderStream = streams.IconvLiteDecoderStream; + module.exports.encodeStream = function encodeStream(encoding, options) { + return new module.exports.IconvLiteEncoderStream(module.exports.getEncoder(encoding, options), options); + }; + module.exports.decodeStream = function decodeStream(encoding, options) { + return new module.exports.IconvLiteDecoderStream(module.exports.getDecoder(encoding, options), options); + }; + module.exports.supportsStreams = true; + }; + var streamModule; + try { + streamModule = __require("stream"); + } catch (e) { + } + if (streamModule && streamModule.Transform) { + module.exports.enableStreamingAPI(streamModule); + } else { + module.exports.encodeStream = module.exports.decodeStream = function() { + throw new Error("iconv-lite Streaming API is not enabled. Use iconv.enableStreamingAPI(require('stream')); to enable it."); + }; + } + if (false) { + console.error("iconv-lite warning: js files use non-utf8 encoding. See https://github.com/ashtuchkin/iconv-lite/wiki/Javascript-source-file-encodings for more info."); + } + } +}); + +// ../node_modules/libmime/lib/charsets.js +var require_charsets2 = __commonJS({ + "../node_modules/libmime/lib/charsets.js"(exports, module) { + "use strict"; + module.exports = { + "866": "IBM866", + "unicode-1-1-utf-8": "UTF-8", + "utf-8": "UTF-8", + utf8: "UTF-8", + cp866: "IBM866", + csibm866: "IBM866", + ibm866: "IBM866", + csisolatin2: "ISO-8859-2", + "iso-8859-2": "ISO-8859-2", + "iso-ir-101": "ISO-8859-2", + "iso8859-2": "ISO-8859-2", + iso88592: "ISO-8859-2", + "iso_8859-2": "ISO-8859-2", + "iso_8859-2:1987": "ISO-8859-2", + l2: "ISO-8859-2", + latin2: "ISO-8859-2", + csisolatin3: "ISO-8859-3", + "iso-8859-3": "ISO-8859-3", + "iso-ir-109": "ISO-8859-3", + "iso8859-3": "ISO-8859-3", + iso88593: "ISO-8859-3", + "iso_8859-3": "ISO-8859-3", + "iso_8859-3:1988": "ISO-8859-3", + l3: "ISO-8859-3", + latin3: "ISO-8859-3", + csisolatin4: "ISO-8859-4", + "iso-8859-4": "ISO-8859-4", + "iso-ir-110": "ISO-8859-4", + "iso8859-4": "ISO-8859-4", + iso88594: "ISO-8859-4", + "iso_8859-4": "ISO-8859-4", + "iso_8859-4:1988": "ISO-8859-4", + l4: "ISO-8859-4", + latin4: "ISO-8859-4", + csisolatincyrillic: "ISO-8859-5", + cyrillic: "ISO-8859-5", + "iso-8859-5": "ISO-8859-5", + "iso-ir-144": "ISO-8859-5", + "iso8859-5": "ISO-8859-5", + iso88595: "ISO-8859-5", + "iso_8859-5": "ISO-8859-5", + "iso_8859-5:1988": "ISO-8859-5", + arabic: "ISO-8859-6", + "asmo-708": "ISO-8859-6", + csiso88596e: "ISO-8859-6", + csiso88596i: "ISO-8859-6", + csisolatinarabic: "ISO-8859-6", + "ecma-114": "ISO-8859-6", + "iso-8859-6": "ISO-8859-6", + "iso-8859-6-e": "ISO-8859-6", + "iso-8859-6-i": "ISO-8859-6", + "iso-ir-127": "ISO-8859-6", + "iso8859-6": "ISO-8859-6", + iso88596: "ISO-8859-6", + "iso_8859-6": "ISO-8859-6", + "iso_8859-6:1987": "ISO-8859-6", + csisolatingreek: "ISO-8859-7", + "ecma-118": "ISO-8859-7", + elot_928: "ISO-8859-7", + greek: "ISO-8859-7", + greek8: "ISO-8859-7", + "iso-8859-7": "ISO-8859-7", + "iso-ir-126": "ISO-8859-7", + "iso8859-7": "ISO-8859-7", + iso88597: "ISO-8859-7", + "iso_8859-7": "ISO-8859-7", + "iso_8859-7:1987": "ISO-8859-7", + sun_eu_greek: "ISO-8859-7", + csiso88598e: "ISO-8859-8", + csisolatinhebrew: "ISO-8859-8", + hebrew: "ISO-8859-8", + "iso-8859-8": "ISO-8859-8", + "iso-8859-8-e": "ISO-8859-8", + "iso-8859-8-i": "ISO-8859-8", + "iso-ir-138": "ISO-8859-8", + "iso8859-8": "ISO-8859-8", + iso88598: "ISO-8859-8", + "iso_8859-8": "ISO-8859-8", + "iso_8859-8:1988": "ISO-8859-8", + visual: "ISO-8859-8", + csisolatin6: "ISO-8859-10", + "iso-8859-10": "ISO-8859-10", + "iso-ir-157": "ISO-8859-10", + "iso8859-10": "ISO-8859-10", + iso885910: "ISO-8859-10", + l6: "ISO-8859-10", + latin6: "ISO-8859-10", + "iso-8859-13": "ISO-8859-13", + "iso8859-13": "ISO-8859-13", + iso885913: "ISO-8859-13", + "iso-8859-14": "ISO-8859-14", + "iso8859-14": "ISO-8859-14", + iso885914: "ISO-8859-14", + csisolatin9: "ISO-8859-15", + "iso-8859-15": "ISO-8859-15", + "iso8859-15": "ISO-8859-15", + iso885915: "ISO-8859-15", + "iso_8859-15": "ISO-8859-15", + l9: "ISO-8859-15", + "iso-8859-16": "ISO-8859-16", + cskoi8r: "KOI8-R", + koi: "KOI8-R", + koi8: "KOI8-R", + "koi8-r": "KOI8-R", + koi8_r: "KOI8-R", + "koi8-ru": "KOI8-U", + "koi8-u": "KOI8-U", + csmacintosh: "macintosh", + mac: "macintosh", + macintosh: "macintosh", + "x-mac-roman": "macintosh", + "dos-874": "windows-874", + "iso-8859-11": "windows-874", + "iso8859-11": "windows-874", + iso885911: "windows-874", + "tis-620": "windows-874", + "windows-874": "windows-874", + cp1250: "windows-1250", + "windows-1250": "windows-1250", + "x-cp1250": "windows-1250", + cp1251: "windows-1251", + "windows-1251": "windows-1251", + "x-cp1251": "windows-1251", + "ansi_x3.4-1968": "windows-1252", + ascii: "windows-1252", + cp1252: "windows-1252", + cp819: "windows-1252", + csisolatin1: "windows-1252", + ibm819: "windows-1252", + "iso-8859-1": "windows-1252", + "iso-ir-100": "windows-1252", + "iso8859-1": "windows-1252", + iso88591: "windows-1252", + "iso_8859-1": "windows-1252", + "iso_8859-1:1987": "windows-1252", + l1: "windows-1252", + latin1: "windows-1252", + "us-ascii": "windows-1252", + "windows-1252": "windows-1252", + "x-cp1252": "windows-1252", + cp1253: "windows-1253", + "windows-1253": "windows-1253", + "x-cp1253": "windows-1253", + cp1254: "windows-1254", + csisolatin5: "windows-1254", + "iso-8859-9": "windows-1254", + "iso-ir-148": "windows-1254", + "iso8859-9": "windows-1254", + iso88599: "windows-1254", + "iso_8859-9": "windows-1254", + "iso_8859-9:1989": "windows-1254", + l5: "windows-1254", + latin5: "windows-1254", + "windows-1254": "windows-1254", + "x-cp1254": "windows-1254", + cp1255: "windows-1255", + "windows-1255": "windows-1255", + "x-cp1255": "windows-1255", + cp1256: "windows-1256", + "windows-1256": "windows-1256", + "x-cp1256": "windows-1256", + cp1257: "windows-1257", + "windows-1257": "windows-1257", + "x-cp1257": "windows-1257", + cp1258: "windows-1258", + "windows-1258": "windows-1258", + "x-cp1258": "windows-1258", + chinese: "GBK", + csgb2312: "GBK", + csiso58gb231280: "GBK", + gb2312: "GBK", + gb_2312: "GBK", + "gb_2312-80": "GBK", + gbk: "GBK", + "iso-ir-58": "GBK", + "x-gbk": "GBK", + gb18030: "gb18030", + big5: "Big5", + "big5-hkscs": "Big5", + "cn-big5": "Big5", + csbig5: "Big5", + "x-x-big5": "Big5", + cseucpkdfmtjapanese: "EUC-JP", + "euc-jp": "EUC-JP", + "x-euc-jp": "EUC-JP", + csshiftjis: "Shift_JIS", + ms932: "Shift_JIS", + ms_kanji: "Shift_JIS", + "shift-jis": "Shift_JIS", + shift_jis: "Shift_JIS", + sjis: "Shift_JIS", + "windows-31j": "Shift_JIS", + "x-sjis": "Shift_JIS", + cseuckr: "EUC-KR", + csksc56011987: "EUC-KR", + "euc-kr": "EUC-KR", + "iso-ir-149": "EUC-KR", + korean: "EUC-KR", + "ks_c_5601-1987": "EUC-KR", + "ks_c_5601-1989": "EUC-KR", + ksc5601: "EUC-KR", + ksc_5601: "EUC-KR", + "windows-949": "EUC-KR", + "utf-16be": "UTF-16BE", + "utf-16": "UTF-16LE", + "utf-16le": "UTF-16LE" + }; + } +}); + +// ../node_modules/libmime/lib/charset.js +var require_charset2 = __commonJS({ + "../node_modules/libmime/lib/charset.js"(exports, module) { + "use strict"; + var { Buffer: Buffer2 } = __require("node:buffer"); + var iconv = require_lib2(); + var encodingJapanese = require_src(); + var charsets = require_charsets2(); + var charset = module.exports = { + /** + * Encodes an unicode string into an Buffer object as UTF-8 + * + * We force UTF-8 here, no strange encodings allowed. + * + * @param {String} str String to be encoded + * @return {Buffer} UTF-8 encoded typed array + */ + encode(str) { + return Buffer2.from(str, "utf-8"); + }, + /** + * Decodes a string from Buffer to an unicode string using specified encoding + * NB! Throws if unknown charset is used + * + * @param {Buffer} buf Binary data to be decoded + * @param {String} [fromCharset='UTF-8'] Binary data is decoded into string using this charset + * @return {String} Decoded string + */ + decode(buf, fromCharset) { + fromCharset = charset.normalizeCharset(fromCharset || "UTF-8"); + if (/^(us-)?ascii|utf-8|7bit$/i.test(fromCharset)) { + return buf.toString("utf-8"); + } + try { + if (/^jis|^iso-?2022-?jp|^EUCJP/i.test(fromCharset)) { + if (typeof buf === "string") { + buf = Buffer2.from(buf); + } + try { + let output = encodingJapanese.convert(buf, { + to: "UNICODE", + from: fromCharset, + type: "string" + }); + if (typeof output === "string") { + return output; + } + } catch (err) { + } + } + return iconv.decode(buf, fromCharset); + } catch (err) { + return buf.toString(); + } + }, + /** + * Convert a string from specific encoding to UTF-8 Buffer + * + * @param {String|Buffer} data String or Buffer to be encoded + * @param {String} [fromCharset='UTF-8'] Source encoding for the string + * @return {Buffer} UTF-8 encoded typed array + */ + convert(data, fromCharset) { + fromCharset = charset.normalizeCharset(fromCharset || "UTF-8"); + let bufString; + if (typeof data !== "string") { + if (/^(us-)?ascii|utf-8|7bit$/i.test(fromCharset)) { + return data; + } + bufString = charset.decode(data, fromCharset); + return charset.encode(bufString); + } + return charset.encode(data); + }, + /** + * Converts well known invalid character set names to proper names. + * eg. win-1257 will be converted to WINDOWS-1257 + * + * @param {String} charset Charset name to convert + * @return {String} Canonicalized charset name + */ + normalizeCharset(charset2) { + charset2 = charset2.toLowerCase().trim(); + if (charsets.hasOwnProperty(charset2) && charsets[charset2]) { + return charsets[charset2]; + } + charset2 = charset2.replace(/^utf[-_]?(\d+)/, "utf-$1").replace(/^(?:us[-_]?)ascii/, "windows-1252").replace(/^win(?:dows)?[-_]?(\d+)/, "windows-$1").replace(/^(?:latin|iso[-_]?8859)?[-_]?(\d+)/, "iso-8859-$1").replace(/^l[-_]?(\d+)/, "iso-8859-$1"); + if (charsets.hasOwnProperty(charset2) && charsets[charset2]) { + return charsets[charset2]; + } + return charset2.toUpperCase(); + } + }; + } +}); + +// ../node_modules/libmime/lib/mimetypes.js +var require_mimetypes2 = __commonJS({ + "../node_modules/libmime/lib/mimetypes.js"(exports, module) { + "use strict"; + module.exports = { + list: { + "application/acad": "dwg", + "application/applixware": "aw", + "application/arj": "arj", + "application/atom+xml": "xml", + "application/atomcat+xml": "atomcat", + "application/atomsvc+xml": "atomsvc", + "application/base64": ["mm", "mme"], + "application/binhex": "hqx", + "application/binhex4": "hqx", + "application/book": ["book", "boo"], + "application/ccxml+xml,": "ccxml", + "application/cdf": "cdf", + "application/cdmi-capability": "cdmia", + "application/cdmi-container": "cdmic", + "application/cdmi-domain": "cdmid", + "application/cdmi-object": "cdmio", + "application/cdmi-queue": "cdmiq", + "application/clariscad": "ccad", + "application/commonground": "dp", + "application/cu-seeme": "cu", + "application/davmount+xml": "davmount", + "application/drafting": "drw", + "application/dsptype": "tsp", + "application/dssc+der": "dssc", + "application/dssc+xml": "xdssc", + "application/dxf": "dxf", + "application/ecmascript": ["js", "es"], + "application/emma+xml": "emma", + "application/envoy": "evy", + "application/epub+zip": "epub", + "application/excel": ["xls", "xl", "xla", "xlb", "xlc", "xld", "xlk", "xll", "xlm", "xlt", "xlv", "xlw"], + "application/exi": "exi", + "application/font-tdpfr": "pfr", + "application/fractals": "fif", + "application/freeloader": "frl", + "application/futuresplash": "spl", + "application/gnutar": "tgz", + "application/groupwise": "vew", + "application/hlp": "hlp", + "application/hta": "hta", + "application/hyperstudio": "stk", + "application/i-deas": "unv", + "application/iges": ["iges", "igs"], + "application/inf": "inf", + "application/internet-property-stream": "acx", + "application/ipfix": "ipfix", + "application/java": "class", + "application/java-archive": "jar", + "application/java-byte-code": "class", + "application/java-serialized-object": "ser", + "application/java-vm": "class", + "application/javascript": "js", + "application/json": "json", + "application/lha": "lha", + "application/lzx": "lzx", + "application/mac-binary": "bin", + "application/mac-binhex": "hqx", + "application/mac-binhex40": "hqx", + "application/mac-compactpro": "cpt", + "application/macbinary": "bin", + "application/mads+xml": "mads", + "application/marc": "mrc", + "application/marcxml+xml": "mrcx", + "application/mathematica": "ma", + "application/mathml+xml": "mathml", + "application/mbedlet": "mbd", + "application/mbox": "mbox", + "application/mcad": "mcd", + "application/mediaservercontrol+xml": "mscml", + "application/metalink4+xml": "meta4", + "application/mets+xml": "mets", + "application/mime": "aps", + "application/mods+xml": "mods", + "application/mp21": "m21", + "application/mp4": "mp4", + "application/mspowerpoint": ["ppt", "pot", "pps", "ppz"], + "application/msword": ["doc", "dot", "w6w", "wiz", "word"], + "application/mswrite": "wri", + "application/mxf": "mxf", + "application/netmc": "mcp", + "application/octet-stream": ["*"], + "application/oda": "oda", + "application/oebps-package+xml": "opf", + "application/ogg": "ogx", + "application/olescript": "axs", + "application/onenote": "onetoc", + "application/patch-ops-error+xml": "xer", + "application/pdf": "pdf", + "application/pgp-encrypted": "asc", + "application/pgp-signature": "pgp", + "application/pics-rules": "prf", + "application/pkcs-12": "p12", + "application/pkcs-crl": "crl", + "application/pkcs10": "p10", + "application/pkcs7-mime": ["p7c", "p7m"], + "application/pkcs7-signature": "p7s", + "application/pkcs8": "p8", + "application/pkix-attr-cert": "ac", + "application/pkix-cert": ["cer", "crt"], + "application/pkix-crl": "crl", + "application/pkix-pkipath": "pkipath", + "application/pkixcmp": "pki", + "application/plain": "text", + "application/pls+xml": "pls", + "application/postscript": ["ps", "ai", "eps"], + "application/powerpoint": "ppt", + "application/pro_eng": ["part", "prt"], + "application/prs.cww": "cww", + "application/pskc+xml": "pskcxml", + "application/rdf+xml": "rdf", + "application/reginfo+xml": "rif", + "application/relax-ng-compact-syntax": "rnc", + "application/resource-lists+xml": "rl", + "application/resource-lists-diff+xml": "rld", + "application/ringing-tones": "rng", + "application/rls-services+xml": "rs", + "application/rsd+xml": "rsd", + "application/rss+xml": "xml", + "application/rtf": ["rtf", "rtx"], + "application/sbml+xml": "sbml", + "application/scvp-cv-request": "scq", + "application/scvp-cv-response": "scs", + "application/scvp-vp-request": "spq", + "application/scvp-vp-response": "spp", + "application/sdp": "sdp", + "application/sea": "sea", + "application/set": "set", + "application/set-payment-initiation": "setpay", + "application/set-registration-initiation": "setreg", + "application/shf+xml": "shf", + "application/sla": "stl", + "application/smil": ["smi", "smil"], + "application/smil+xml": "smi", + "application/solids": "sol", + "application/sounder": "sdr", + "application/sparql-query": "rq", + "application/sparql-results+xml": "srx", + "application/srgs": "gram", + "application/srgs+xml": "grxml", + "application/sru+xml": "sru", + "application/ssml+xml": "ssml", + "application/step": ["step", "stp"], + "application/streamingmedia": "ssm", + "application/tei+xml": "tei", + "application/thraud+xml": "tfi", + "application/timestamped-data": "tsd", + "application/toolbook": "tbk", + "application/vda": "vda", + "application/vnd.3gpp.pic-bw-large": "plb", + "application/vnd.3gpp.pic-bw-small": "psb", + "application/vnd.3gpp.pic-bw-var": "pvb", + "application/vnd.3gpp2.tcap": "tcap", + "application/vnd.3m.post-it-notes": "pwn", + "application/vnd.accpac.simply.aso": "aso", + "application/vnd.accpac.simply.imp": "imp", + "application/vnd.acucobol": "acu", + "application/vnd.acucorp": "atc", + "application/vnd.adobe.air-application-installer-package+zip": "air", + "application/vnd.adobe.fxp": "fxp", + "application/vnd.adobe.xdp+xml": "xdp", + "application/vnd.adobe.xfdf": "xfdf", + "application/vnd.ahead.space": "ahead", + "application/vnd.airzip.filesecure.azf": "azf", + "application/vnd.airzip.filesecure.azs": "azs", + "application/vnd.amazon.ebook": "azw", + "application/vnd.americandynamics.acc": "acc", + "application/vnd.amiga.ami": "ami", + "application/vnd.android.package-archive": "apk", + "application/vnd.anser-web-certificate-issue-initiation": "cii", + "application/vnd.anser-web-funds-transfer-initiation": "fti", + "application/vnd.antix.game-component": "atx", + "application/vnd.apple.installer+xml": "mpkg", + "application/vnd.apple.mpegurl": "m3u8", + "application/vnd.aristanetworks.swi": "swi", + "application/vnd.audiograph": "aep", + "application/vnd.blueice.multipass": "mpm", + "application/vnd.bmi": "bmi", + "application/vnd.businessobjects": "rep", + "application/vnd.chemdraw+xml": "cdxml", + "application/vnd.chipnuts.karaoke-mmd": "mmd", + "application/vnd.cinderella": "cdy", + "application/vnd.claymore": "cla", + "application/vnd.cloanto.rp9": "rp9", + "application/vnd.clonk.c4group": "c4g", + "application/vnd.cluetrust.cartomobile-config": "c11amc", + "application/vnd.cluetrust.cartomobile-config-pkg": "c11amz", + "application/vnd.commonspace": "csp", + "application/vnd.contact.cmsg": "cdbcmsg", + "application/vnd.cosmocaller": "cmc", + "application/vnd.crick.clicker": "clkx", + "application/vnd.crick.clicker.keyboard": "clkk", + "application/vnd.crick.clicker.palette": "clkp", + "application/vnd.crick.clicker.template": "clkt", + "application/vnd.crick.clicker.wordbank": "clkw", + "application/vnd.criticaltools.wbs+xml": "wbs", + "application/vnd.ctc-posml": "pml", + "application/vnd.cups-ppd": "ppd", + "application/vnd.curl.car": "car", + "application/vnd.curl.pcurl": "pcurl", + "application/vnd.data-vision.rdz": "rdz", + "application/vnd.denovo.fcselayout-link": "fe_launch", + "application/vnd.dna": "dna", + "application/vnd.dolby.mlp": "mlp", + "application/vnd.dpgraph": "dpg", + "application/vnd.dreamfactory": "dfac", + "application/vnd.dvb.ait": "ait", + "application/vnd.dvb.service": "svc", + "application/vnd.dynageo": "geo", + "application/vnd.ecowin.chart": "mag", + "application/vnd.enliven": "nml", + "application/vnd.epson.esf": "esf", + "application/vnd.epson.msf": "msf", + "application/vnd.epson.quickanime": "qam", + "application/vnd.epson.salt": "slt", + "application/vnd.epson.ssf": "ssf", + "application/vnd.eszigno3+xml": "es3", + "application/vnd.ezpix-album": "ez2", + "application/vnd.ezpix-package": "ez3", + "application/vnd.fdf": "fdf", + "application/vnd.fdsn.seed": "seed", + "application/vnd.flographit": "gph", + "application/vnd.fluxtime.clip": "ftc", + "application/vnd.framemaker": "fm", + "application/vnd.frogans.fnc": "fnc", + "application/vnd.frogans.ltf": "ltf", + "application/vnd.fsc.weblaunch": "fsc", + "application/vnd.fujitsu.oasys": "oas", + "application/vnd.fujitsu.oasys2": "oa2", + "application/vnd.fujitsu.oasys3": "oa3", + "application/vnd.fujitsu.oasysgp": "fg5", + "application/vnd.fujitsu.oasysprs": "bh2", + "application/vnd.fujixerox.ddd": "ddd", + "application/vnd.fujixerox.docuworks": "xdw", + "application/vnd.fujixerox.docuworks.binder": "xbd", + "application/vnd.fuzzysheet": "fzs", + "application/vnd.genomatix.tuxedo": "txd", + "application/vnd.geogebra.file": "ggb", + "application/vnd.geogebra.tool": "ggt", + "application/vnd.geometry-explorer": "gex", + "application/vnd.geonext": "gxt", + "application/vnd.geoplan": "g2w", + "application/vnd.geospace": "g3w", + "application/vnd.gmx": "gmx", + "application/vnd.google-earth.kml+xml": "kml", + "application/vnd.google-earth.kmz": "kmz", + "application/vnd.grafeq": "gqf", + "application/vnd.groove-account": "gac", + "application/vnd.groove-help": "ghf", + "application/vnd.groove-identity-message": "gim", + "application/vnd.groove-injector": "grv", + "application/vnd.groove-tool-message": "gtm", + "application/vnd.groove-tool-template": "tpl", + "application/vnd.groove-vcard": "vcg", + "application/vnd.hal+xml": "hal", + "application/vnd.handheld-entertainment+xml": "zmm", + "application/vnd.hbci": "hbci", + "application/vnd.hhe.lesson-player": "les", + "application/vnd.hp-hpgl": ["hgl", "hpg", "hpgl"], + "application/vnd.hp-hpid": "hpid", + "application/vnd.hp-hps": "hps", + "application/vnd.hp-jlyt": "jlt", + "application/vnd.hp-pcl": "pcl", + "application/vnd.hp-pclxl": "pclxl", + "application/vnd.hydrostatix.sof-data": "sfd-hdstx", + "application/vnd.hzn-3d-crossword": "x3d", + "application/vnd.ibm.minipay": "mpy", + "application/vnd.ibm.modcap": "afp", + "application/vnd.ibm.rights-management": "irm", + "application/vnd.ibm.secure-container": "sc", + "application/vnd.iccprofile": "icc", + "application/vnd.igloader": "igl", + "application/vnd.immervision-ivp": "ivp", + "application/vnd.immervision-ivu": "ivu", + "application/vnd.insors.igm": "igm", + "application/vnd.intercon.formnet": "xpw", + "application/vnd.intergeo": "i2g", + "application/vnd.intu.qbo": "qbo", + "application/vnd.intu.qfx": "qfx", + "application/vnd.ipunplugged.rcprofile": "rcprofile", + "application/vnd.irepository.package+xml": "irp", + "application/vnd.is-xpr": "xpr", + "application/vnd.isac.fcs": "fcs", + "application/vnd.jam": "jam", + "application/vnd.jcp.javame.midlet-rms": "rms", + "application/vnd.jisp": "jisp", + "application/vnd.joost.joda-archive": "joda", + "application/vnd.kahootz": "ktz", + "application/vnd.kde.karbon": "karbon", + "application/vnd.kde.kchart": "chrt", + "application/vnd.kde.kformula": "kfo", + "application/vnd.kde.kivio": "flw", + "application/vnd.kde.kontour": "kon", + "application/vnd.kde.kpresenter": "kpr", + "application/vnd.kde.kspread": "ksp", + "application/vnd.kde.kword": "kwd", + "application/vnd.kenameaapp": "htke", + "application/vnd.kidspiration": "kia", + "application/vnd.kinar": "kne", + "application/vnd.koan": "skp", + "application/vnd.kodak-descriptor": "sse", + "application/vnd.las.las+xml": "lasxml", + "application/vnd.llamagraphics.life-balance.desktop": "lbd", + "application/vnd.llamagraphics.life-balance.exchange+xml": "lbe", + "application/vnd.lotus-1-2-3": "123", + "application/vnd.lotus-approach": "apr", + "application/vnd.lotus-freelance": "pre", + "application/vnd.lotus-notes": "nsf", + "application/vnd.lotus-organizer": "org", + "application/vnd.lotus-screencam": "scm", + "application/vnd.lotus-wordpro": "lwp", + "application/vnd.macports.portpkg": "portpkg", + "application/vnd.mcd": "mcd", + "application/vnd.medcalcdata": "mc1", + "application/vnd.mediastation.cdkey": "cdkey", + "application/vnd.mfer": "mwf", + "application/vnd.mfmp": "mfm", + "application/vnd.micrografx.flo": "flo", + "application/vnd.micrografx.igx": "igx", + "application/vnd.mif": "mif", + "application/vnd.mobius.daf": "daf", + "application/vnd.mobius.dis": "dis", + "application/vnd.mobius.mbk": "mbk", + "application/vnd.mobius.mqy": "mqy", + "application/vnd.mobius.msl": "msl", + "application/vnd.mobius.plc": "plc", + "application/vnd.mobius.txf": "txf", + "application/vnd.mophun.application": "mpn", + "application/vnd.mophun.certificate": "mpc", + "application/vnd.mozilla.xul+xml": "xul", + "application/vnd.ms-artgalry": "cil", + "application/vnd.ms-cab-compressed": "cab", + "application/vnd.ms-excel": ["xls", "xla", "xlc", "xlm", "xlt", "xlw", "xlb", "xll"], + "application/vnd.ms-excel.addin.macroenabled.12": "xlam", + "application/vnd.ms-excel.sheet.binary.macroenabled.12": "xlsb", + "application/vnd.ms-excel.sheet.macroenabled.12": "xlsm", + "application/vnd.ms-excel.template.macroenabled.12": "xltm", + "application/vnd.ms-fontobject": "eot", + "application/vnd.ms-htmlhelp": "chm", + "application/vnd.ms-ims": "ims", + "application/vnd.ms-lrm": "lrm", + "application/vnd.ms-officetheme": "thmx", + "application/vnd.ms-outlook": "msg", + "application/vnd.ms-pki.certstore": "sst", + "application/vnd.ms-pki.pko": "pko", + "application/vnd.ms-pki.seccat": "cat", + "application/vnd.ms-pki.stl": "stl", + "application/vnd.ms-pkicertstore": "sst", + "application/vnd.ms-pkiseccat": "cat", + "application/vnd.ms-pkistl": "stl", + "application/vnd.ms-powerpoint": ["ppt", "pot", "pps", "ppa", "pwz"], + "application/vnd.ms-powerpoint.addin.macroenabled.12": "ppam", + "application/vnd.ms-powerpoint.presentation.macroenabled.12": "pptm", + "application/vnd.ms-powerpoint.slide.macroenabled.12": "sldm", + "application/vnd.ms-powerpoint.slideshow.macroenabled.12": "ppsm", + "application/vnd.ms-powerpoint.template.macroenabled.12": "potm", + "application/vnd.ms-project": "mpp", + "application/vnd.ms-word.document.macroenabled.12": "docm", + "application/vnd.ms-word.template.macroenabled.12": "dotm", + "application/vnd.ms-works": ["wks", "wcm", "wdb", "wps"], + "application/vnd.ms-wpl": "wpl", + "application/vnd.ms-xpsdocument": "xps", + "application/vnd.mseq": "mseq", + "application/vnd.musician": "mus", + "application/vnd.muvee.style": "msty", + "application/vnd.neurolanguage.nlu": "nlu", + "application/vnd.noblenet-directory": "nnd", + "application/vnd.noblenet-sealer": "nns", + "application/vnd.noblenet-web": "nnw", + "application/vnd.nokia.configuration-message": "ncm", + "application/vnd.nokia.n-gage.data": "ngdat", + "application/vnd.nokia.n-gage.symbian.install": "n-gage", + "application/vnd.nokia.radio-preset": "rpst", + "application/vnd.nokia.radio-presets": "rpss", + "application/vnd.nokia.ringing-tone": "rng", + "application/vnd.novadigm.edm": "edm", + "application/vnd.novadigm.edx": "edx", + "application/vnd.novadigm.ext": "ext", + "application/vnd.oasis.opendocument.chart": "odc", + "application/vnd.oasis.opendocument.chart-template": "otc", + "application/vnd.oasis.opendocument.database": "odb", + "application/vnd.oasis.opendocument.formula": "odf", + "application/vnd.oasis.opendocument.formula-template": "odft", + "application/vnd.oasis.opendocument.graphics": "odg", + "application/vnd.oasis.opendocument.graphics-template": "otg", + "application/vnd.oasis.opendocument.image": "odi", + "application/vnd.oasis.opendocument.image-template": "oti", + "application/vnd.oasis.opendocument.presentation": "odp", + "application/vnd.oasis.opendocument.presentation-template": "otp", + "application/vnd.oasis.opendocument.spreadsheet": "ods", + "application/vnd.oasis.opendocument.spreadsheet-template": "ots", + "application/vnd.oasis.opendocument.text": "odt", + "application/vnd.oasis.opendocument.text-master": "odm", + "application/vnd.oasis.opendocument.text-template": "ott", + "application/vnd.oasis.opendocument.text-web": "oth", + "application/vnd.olpc-sugar": "xo", + "application/vnd.oma.dd2+xml": "dd2", + "application/vnd.openofficeorg.extension": "oxt", + "application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx", + "application/vnd.openxmlformats-officedocument.presentationml.slide": "sldx", + "application/vnd.openxmlformats-officedocument.presentationml.slideshow": "ppsx", + "application/vnd.openxmlformats-officedocument.presentationml.template": "potx", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx", + "application/vnd.openxmlformats-officedocument.spreadsheetml.template": "xltx", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx", + "application/vnd.openxmlformats-officedocument.wordprocessingml.template": "dotx", + "application/vnd.osgeo.mapguide.package": "mgp", + "application/vnd.osgi.dp": "dp", + "application/vnd.palm": "pdb", + "application/vnd.pawaafile": "paw", + "application/vnd.pg.format": "str", + "application/vnd.pg.osasli": "ei6", + "application/vnd.picsel": "efif", + "application/vnd.pmi.widget": "wg", + "application/vnd.pocketlearn": "plf", + "application/vnd.powerbuilder6": "pbd", + "application/vnd.previewsystems.box": "box", + "application/vnd.proteus.magazine": "mgz", + "application/vnd.publishare-delta-tree": "qps", + "application/vnd.pvi.ptid1": "ptid", + "application/vnd.quark.quarkxpress": "qxd", + "application/vnd.realvnc.bed": "bed", + "application/vnd.recordare.musicxml": "mxl", + "application/vnd.recordare.musicxml+xml": "musicxml", + "application/vnd.rig.cryptonote": "cryptonote", + "application/vnd.rim.cod": "cod", + "application/vnd.rn-realmedia": "rm", + "application/vnd.rn-realplayer": "rnx", + "application/vnd.route66.link66+xml": "link66", + "application/vnd.sailingtracker.track": "st", + "application/vnd.seemail": "see", + "application/vnd.sema": "sema", + "application/vnd.semd": "semd", + "application/vnd.semf": "semf", + "application/vnd.shana.informed.formdata": "ifm", + "application/vnd.shana.informed.formtemplate": "itp", + "application/vnd.shana.informed.interchange": "iif", + "application/vnd.shana.informed.package": "ipk", + "application/vnd.simtech-mindmapper": "twd", + "application/vnd.smaf": "mmf", + "application/vnd.smart.teacher": "teacher", + "application/vnd.solent.sdkm+xml": "sdkm", + "application/vnd.spotfire.dxp": "dxp", + "application/vnd.spotfire.sfs": "sfs", + "application/vnd.stardivision.calc": "sdc", + "application/vnd.stardivision.draw": "sda", + "application/vnd.stardivision.impress": "sdd", + "application/vnd.stardivision.math": "smf", + "application/vnd.stardivision.writer": "sdw", + "application/vnd.stardivision.writer-global": "sgl", + "application/vnd.stepmania.stepchart": "sm", + "application/vnd.sun.xml.calc": "sxc", + "application/vnd.sun.xml.calc.template": "stc", + "application/vnd.sun.xml.draw": "sxd", + "application/vnd.sun.xml.draw.template": "std", + "application/vnd.sun.xml.impress": "sxi", + "application/vnd.sun.xml.impress.template": "sti", + "application/vnd.sun.xml.math": "sxm", + "application/vnd.sun.xml.writer": "sxw", + "application/vnd.sun.xml.writer.global": "sxg", + "application/vnd.sun.xml.writer.template": "stw", + "application/vnd.sus-calendar": "sus", + "application/vnd.svd": "svd", + "application/vnd.symbian.install": "sis", + "application/vnd.syncml+xml": "xsm", + "application/vnd.syncml.dm+wbxml": "bdm", + "application/vnd.syncml.dm+xml": "xdm", + "application/vnd.tao.intent-module-archive": "tao", + "application/vnd.tmobile-livetv": "tmo", + "application/vnd.trid.tpt": "tpt", + "application/vnd.triscape.mxs": "mxs", + "application/vnd.trueapp": "tra", + "application/vnd.ufdl": "ufd", + "application/vnd.uiq.theme": "utz", + "application/vnd.umajin": "umj", + "application/vnd.unity": "unityweb", + "application/vnd.uoml+xml": "uoml", + "application/vnd.vcx": "vcx", + "application/vnd.visio": "vsd", + "application/vnd.visionary": "vis", + "application/vnd.vsf": "vsf", + "application/vnd.wap.wbxml": "wbxml", + "application/vnd.wap.wmlc": "wmlc", + "application/vnd.wap.wmlscriptc": "wmlsc", + "application/vnd.webturbo": "wtb", + "application/vnd.wolfram.player": "nbp", + "application/vnd.wordperfect": "wpd", + "application/vnd.wqd": "wqd", + "application/vnd.wt.stf": "stf", + "application/vnd.xara": ["web", "xar"], + "application/vnd.xfdl": "xfdl", + "application/vnd.yamaha.hv-dic": "hvd", + "application/vnd.yamaha.hv-script": "hvs", + "application/vnd.yamaha.hv-voice": "hvp", + "application/vnd.yamaha.openscoreformat": "osf", + "application/vnd.yamaha.openscoreformat.osfpvg+xml": "osfpvg", + "application/vnd.yamaha.smaf-audio": "saf", + "application/vnd.yamaha.smaf-phrase": "spf", + "application/vnd.yellowriver-custom-menu": "cmp", + "application/vnd.zul": "zir", + "application/vnd.zzazz.deck+xml": "zaz", + "application/vocaltec-media-desc": "vmd", + "application/vocaltec-media-file": "vmf", + "application/voicexml+xml": "vxml", + "application/widget": "wgt", + "application/winhlp": "hlp", + "application/wordperfect": ["wp", "wp5", "wp6", "wpd"], + "application/wordperfect6.0": ["w60", "wp5"], + "application/wordperfect6.1": "w61", + "application/wsdl+xml": "wsdl", + "application/wspolicy+xml": "wspolicy", + "application/x-123": "wk1", + "application/x-7z-compressed": "7z", + "application/x-abiword": "abw", + "application/x-ace-compressed": "ace", + "application/x-aim": "aim", + "application/x-authorware-bin": "aab", + "application/x-authorware-map": "aam", + "application/x-authorware-seg": "aas", + "application/x-bcpio": "bcpio", + "application/x-binary": "bin", + "application/x-binhex40": "hqx", + "application/x-bittorrent": "torrent", + "application/x-bsh": ["bsh", "sh", "shar"], + "application/x-bytecode.elisp": "elc", + "applicaiton/x-bytecode.python": "pyc", + "application/x-bzip": "bz", + "application/x-bzip2": ["boz", "bz2"], + "application/x-cdf": "cdf", + "application/x-cdlink": "vcd", + "application/x-chat": ["cha", "chat"], + "application/x-chess-pgn": "pgn", + "application/x-cmu-raster": "ras", + "application/x-cocoa": "cco", + "application/x-compactpro": "cpt", + "application/x-compress": "z", + "application/x-compressed": ["tgz", "gz", "z", "zip"], + "application/x-conference": "nsc", + "application/x-cpio": "cpio", + "application/x-cpt": "cpt", + "application/x-csh": "csh", + "application/x-debian-package": "deb", + "application/x-deepv": "deepv", + "application/x-director": ["dir", "dcr", "dxr"], + "application/x-doom": "wad", + "application/x-dtbncx+xml": "ncx", + "application/x-dtbook+xml": "dtb", + "application/x-dtbresource+xml": "res", + "application/x-dvi": "dvi", + "application/x-elc": "elc", + "application/x-envoy": ["env", "evy"], + "application/x-esrehber": "es", + "application/x-excel": ["xls", "xla", "xlb", "xlc", "xld", "xlk", "xll", "xlm", "xlt", "xlv", "xlw"], + "application/x-font-bdf": "bdf", + "application/x-font-ghostscript": "gsf", + "application/x-font-linux-psf": "psf", + "application/x-font-otf": "otf", + "application/x-font-pcf": "pcf", + "application/x-font-snf": "snf", + "application/x-font-ttf": "ttf", + "application/x-font-type1": "pfa", + "application/x-font-woff": "woff", + "application/x-frame": "mif", + "application/x-freelance": "pre", + "application/x-futuresplash": "spl", + "application/x-gnumeric": "gnumeric", + "application/x-gsp": "gsp", + "application/x-gss": "gss", + "application/x-gtar": "gtar", + "application/x-gzip": ["gz", "gzip"], + "application/x-hdf": "hdf", + "application/x-helpfile": ["help", "hlp"], + "application/x-httpd-imap": "imap", + "application/x-ima": "ima", + "application/x-internet-signup": ["ins", "isp"], + "application/x-internett-signup": "ins", + "application/x-inventor": "iv", + "application/x-ip2": "ip", + "application/x-iphone": "iii", + "application/x-java-class": "class", + "application/x-java-commerce": "jcm", + "application/x-java-jnlp-file": "jnlp", + "application/x-javascript": "js", + "application/x-koan": ["skd", "skm", "skp", "skt"], + "application/x-ksh": "ksh", + "application/x-latex": ["latex", "ltx"], + "application/x-lha": "lha", + "application/x-lisp": "lsp", + "application/x-livescreen": "ivy", + "application/x-lotus": "wq1", + "application/x-lotusscreencam": "scm", + "application/x-lzh": "lzh", + "application/x-lzx": "lzx", + "application/x-mac-binhex40": "hqx", + "application/x-macbinary": "bin", + "application/x-magic-cap-package-1.0": "mc$", + "application/x-mathcad": "mcd", + "application/x-meme": "mm", + "application/x-midi": ["mid", "midi"], + "application/x-mif": "mif", + "application/x-mix-transfer": "nix", + "application/x-mobipocket-ebook": "prc", + "application/x-mplayer2": "asx", + "application/x-ms-application": "application", + "application/x-ms-wmd": "wmd", + "application/x-ms-wmz": "wmz", + "application/x-ms-xbap": "xbap", + "application/x-msaccess": "mdb", + "application/x-msbinder": "obd", + "application/x-mscardfile": "crd", + "application/x-msclip": "clp", + "application/x-msdownload": ["exe", "dll"], + "application/x-msexcel": ["xls", "xla", "xlw"], + "application/x-msmediaview": ["mvb", "m13", "m14"], + "application/x-msmetafile": "wmf", + "application/x-msmoney": "mny", + "application/x-mspowerpoint": "ppt", + "application/x-mspublisher": "pub", + "application/x-msschedule": "scd", + "application/x-msterminal": "trm", + "application/x-mswrite": "wri", + "application/x-navi-animation": "ani", + "application/x-navidoc": "nvd", + "application/x-navimap": "map", + "application/x-navistyle": "stl", + "application/x-netcdf": ["cdf", "nc"], + "application/x-newton-compatible-pkg": "pkg", + "application/x-nokia-9000-communicator-add-on-software": "aos", + "application/x-omc": "omc", + "application/x-omcdatamaker": "omcd", + "application/x-omcregerator": "omcr", + "application/x-pagemaker": ["pm4", "pm5"], + "application/x-pcl": "pcl", + "application/x-perfmon": ["pma", "pmc", "pml", "pmr", "pmw"], + "application/x-pixclscript": "plx", + "application/x-pkcs10": "p10", + "application/x-pkcs12": ["p12", "pfx"], + "application/x-pkcs7-certificates": ["p7b", "spc"], + "application/x-pkcs7-certreqresp": "p7r", + "application/x-pkcs7-mime": ["p7m", "p7c"], + "application/x-pkcs7-signature": ["p7s", "p7a"], + "application/x-pointplus": "css", + "application/x-portable-anymap": "pnm", + "application/x-project": ["mpc", "mpt", "mpv", "mpx"], + "application/x-qpro": "wb1", + "application/x-rar-compressed": "rar", + "application/x-rtf": "rtf", + "application/x-sdp": "sdp", + "application/x-sea": "sea", + "application/x-seelogo": "sl", + "application/x-sh": "sh", + "application/x-shar": ["shar", "sh"], + "application/x-shockwave-flash": "swf", + "application/x-silverlight-app": "xap", + "application/x-sit": "sit", + "application/x-sprite": ["spr", "sprite"], + "application/x-stuffit": "sit", + "application/x-stuffitx": "sitx", + "application/x-sv4cpio": "sv4cpio", + "application/x-sv4crc": "sv4crc", + "application/x-tar": "tar", + "application/x-tbook": ["sbk", "tbk"], + "application/x-tcl": "tcl", + "application/x-tex": "tex", + "application/x-tex-tfm": "tfm", + "application/x-texinfo": ["texi", "texinfo"], + "application/x-troff": ["roff", "t", "tr"], + "application/x-troff-man": "man", + "application/x-troff-me": "me", + "application/x-troff-ms": "ms", + "application/x-troff-msvideo": "avi", + "application/x-ustar": "ustar", + "application/x-visio": ["vsd", "vst", "vsw"], + "application/x-vnd.audioexplosion.mzz": "mzz", + "application/x-vnd.ls-xpix": "xpix", + "application/x-vrml": "vrml", + "application/x-wais-source": ["src", "wsrc"], + "application/x-winhelp": "hlp", + "application/x-wintalk": "wtk", + "application/x-world": ["wrl", "svr"], + "application/x-wpwin": "wpd", + "application/x-wri": "wri", + "application/x-x509-ca-cert": ["cer", "crt", "der"], + "application/x-x509-user-cert": "crt", + "application/x-xfig": "fig", + "application/x-xpinstall": "xpi", + "application/x-zip-compressed": "zip", + "application/xcap-diff+xml": "xdf", + "application/xenc+xml": "xenc", + "application/xhtml+xml": "xhtml", + "application/xml": "xml", + "application/xml-dtd": "dtd", + "application/xop+xml": "xop", + "application/xslt+xml": "xslt", + "application/xspf+xml": "xspf", + "application/xv+xml": "mxml", + "application/yang": "yang", + "application/yin+xml": "yin", + "application/ynd.ms-pkipko": "pko", + "application/zip": "zip", + "audio/adpcm": "adp", + "audio/aiff": ["aiff", "aif", "aifc"], + "audio/basic": ["snd", "au"], + "audio/it": "it", + "audio/make": ["funk", "my", "pfunk"], + "audio/make.my.funk": "pfunk", + "audio/mid": ["mid", "rmi"], + "audio/midi": ["midi", "kar", "mid"], + "audio/mod": "mod", + "audio/mp4": "mp4a", + "audio/mpeg": ["mpga", "mp3", "m2a", "mp2", "mpa", "mpg"], + "audio/mpeg3": "mp3", + "audio/nspaudio": ["la", "lma"], + "audio/ogg": "oga", + "audio/s3m": "s3m", + "audio/tsp-audio": "tsi", + "audio/tsplayer": "tsp", + "audio/vnd.dece.audio": "uva", + "audio/vnd.digital-winds": "eol", + "audio/vnd.dra": "dra", + "audio/vnd.dts": "dts", + "audio/vnd.dts.hd": "dtshd", + "audio/vnd.lucent.voice": "lvp", + "audio/vnd.ms-playready.media.pya": "pya", + "audio/vnd.nuera.ecelp4800": "ecelp4800", + "audio/vnd.nuera.ecelp7470": "ecelp7470", + "audio/vnd.nuera.ecelp9600": "ecelp9600", + "audio/vnd.qcelp": "qcp", + "audio/vnd.rip": "rip", + "audio/voc": "voc", + "audio/voxware": "vox", + "audio/wav": "wav", + "audio/webm": "weba", + "audio/x-aac": "aac", + "audio/x-adpcm": "snd", + "audio/x-aiff": ["aiff", "aif", "aifc"], + "audio/x-au": "au", + "audio/x-gsm": ["gsd", "gsm"], + "audio/x-jam": "jam", + "audio/x-liveaudio": "lam", + "audio/x-mid": ["mid", "midi"], + "audio/x-midi": ["midi", "mid"], + "audio/x-mod": "mod", + "audio/x-mpeg": "mp2", + "audio/x-mpeg-3": "mp3", + "audio/x-mpegurl": "m3u", + "audio/x-mpequrl": "m3u", + "audio/x-ms-wax": "wax", + "audio/x-ms-wma": "wma", + "audio/x-nspaudio": ["la", "lma"], + "audio/x-pn-realaudio": ["ra", "ram", "rm", "rmm", "rmp"], + "audio/x-pn-realaudio-plugin": ["ra", "rmp", "rpm"], + "audio/x-psid": "sid", + "audio/x-realaudio": "ra", + "audio/x-twinvq": "vqf", + "audio/x-twinvq-plugin": ["vqe", "vql"], + "audio/x-vnd.audioexplosion.mjuicemediafile": "mjf", + "audio/x-voc": "voc", + "audio/x-wav": "wav", + "audio/xm": "xm", + "chemical/x-cdx": "cdx", + "chemical/x-cif": "cif", + "chemical/x-cmdf": "cmdf", + "chemical/x-cml": "cml", + "chemical/x-csml": "csml", + "chemical/x-pdb": ["pdb", "xyz"], + "chemical/x-xyz": "xyz", + "drawing/x-dwf": "dwf", + "i-world/i-vrml": "ivr", + "image/bmp": ["bmp", "bm"], + "image/cgm": "cgm", + "image/cis-cod": "cod", + "image/cmu-raster": ["ras", "rast"], + "image/fif": "fif", + "image/florian": ["flo", "turbot"], + "image/g3fax": "g3", + "image/gif": "gif", + "image/ief": ["ief", "iefs"], + "image/jpeg": ["jpeg", "jpe", "jpg", "jfif", "jfif-tbnl"], + "image/jutvision": "jut", + "image/ktx": "ktx", + "image/naplps": ["nap", "naplps"], + "image/pict": ["pic", "pict"], + "image/pipeg": "jfif", + "image/pjpeg": ["jfif", "jpe", "jpeg", "jpg"], + "image/png": ["png", "x-png"], + "image/prs.btif": "btif", + "image/svg+xml": "svg", + "image/tiff": ["tif", "tiff"], + "image/vasa": "mcf", + "image/vnd.adobe.photoshop": "psd", + "image/vnd.dece.graphic": "uvi", + "image/vnd.djvu": "djvu", + "image/vnd.dvb.subtitle": "sub", + "image/vnd.dwg": ["dwg", "dxf", "svf"], + "image/vnd.dxf": "dxf", + "image/vnd.fastbidsheet": "fbs", + "image/vnd.fpx": "fpx", + "image/vnd.fst": "fst", + "image/vnd.fujixerox.edmics-mmr": "mmr", + "image/vnd.fujixerox.edmics-rlc": "rlc", + "image/vnd.ms-modi": "mdi", + "image/vnd.net-fpx": ["fpx", "npx"], + "image/vnd.rn-realflash": "rf", + "image/vnd.rn-realpix": "rp", + "image/vnd.wap.wbmp": "wbmp", + "image/vnd.xiff": "xif", + "image/webp": "webp", + "image/x-cmu-raster": "ras", + "image/x-cmx": "cmx", + "image/x-dwg": ["dwg", "dxf", "svf"], + "image/x-freehand": "fh", + "image/x-icon": "ico", + "image/x-jg": "art", + "image/x-jps": "jps", + "image/x-niff": ["niff", "nif"], + "image/x-pcx": "pcx", + "image/x-pict": ["pct", "pic"], + "image/x-portable-anymap": "pnm", + "image/x-portable-bitmap": "pbm", + "image/x-portable-graymap": "pgm", + "image/x-portable-greymap": "pgm", + "image/x-portable-pixmap": "ppm", + "image/x-quicktime": ["qif", "qti", "qtif"], + "image/x-rgb": "rgb", + "image/x-tiff": ["tif", "tiff"], + "image/x-windows-bmp": "bmp", + "image/x-xbitmap": "xbm", + "image/x-xbm": "xbm", + "image/x-xpixmap": ["xpm", "pm"], + "image/x-xwd": "xwd", + "image/x-xwindowdump": "xwd", + "image/xbm": "xbm", + "image/xpm": "xpm", + "message/rfc822": ["eml", "mht", "mhtml", "nws", "mime"], + "model/iges": ["iges", "igs"], + "model/mesh": "msh", + "model/vnd.collada+xml": "dae", + "model/vnd.dwf": "dwf", + "model/vnd.gdl": "gdl", + "model/vnd.gtw": "gtw", + "model/vnd.mts": "mts", + "model/vnd.vtu": "vtu", + "model/vrml": ["vrml", "wrl", "wrz"], + "model/x-pov": "pov", + "multipart/x-gzip": "gzip", + "multipart/x-ustar": "ustar", + "multipart/x-zip": "zip", + "music/crescendo": ["mid", "midi"], + "music/x-karaoke": "kar", + "paleovu/x-pv": "pvu", + "text/asp": "asp", + "text/calendar": "ics", + "text/css": "css", + "text/csv": "csv", + "text/ecmascript": "js", + "text/h323": "323", + "text/html": ["html", "htm", "stm", "acgi", "htmls", "htx", "shtml"], + "text/iuls": "uls", + "text/javascript": "js", + "text/mcf": "mcf", + "text/n3": "n3", + "text/pascal": "pas", + "text/plain": [ + "txt", + "bas", + "c", + "h", + "c++", + "cc", + "com", + "conf", + "cxx", + "def", + "f", + "f90", + "for", + "g", + "hh", + "idc", + "jav", + "java", + "list", + "log", + "lst", + "m", + "mar", + "pl", + "sdml", + "text" + ], + "text/plain-bas": "par", + "text/prs.lines.tag": "dsc", + "text/richtext": ["rtx", "rt", "rtf"], + "text/scriplet": "wsc", + "text/scriptlet": "sct", + "text/sgml": ["sgm", "sgml"], + "text/tab-separated-values": "tsv", + "text/troff": "t", + "text/turtle": "ttl", + "text/uri-list": ["uni", "unis", "uri", "uris"], + "text/vnd.abc": "abc", + "text/vnd.curl": "curl", + "text/vnd.curl.dcurl": "dcurl", + "text/vnd.curl.mcurl": "mcurl", + "text/vnd.curl.scurl": "scurl", + "text/vnd.fly": "fly", + "text/vnd.fmi.flexstor": "flx", + "text/vnd.graphviz": "gv", + "text/vnd.in3d.3dml": "3dml", + "text/vnd.in3d.spot": "spot", + "text/vnd.rn-realtext": "rt", + "text/vnd.sun.j2me.app-descriptor": "jad", + "text/vnd.wap.wml": "wml", + "text/vnd.wap.wmlscript": "wmls", + "text/webviewhtml": "htt", + "text/x-asm": ["asm", "s"], + "text/x-audiosoft-intra": "aip", + "text/x-c": ["c", "cc", "cpp"], + "text/x-component": "htc", + "text/x-fortran": ["for", "f", "f77", "f90"], + "text/x-h": ["h", "hh"], + "text/x-java-source": ["java", "jav"], + "text/x-java-source,java": "java", + "text/x-la-asf": "lsx", + "text/x-m": "m", + "text/x-pascal": "p", + "text/x-script": "hlb", + "text/x-script.csh": "csh", + "text/x-script.elisp": "el", + "text/x-script.guile": "scm", + "text/x-script.ksh": "ksh", + "text/x-script.lisp": "lsp", + "text/x-script.perl": "pl", + "text/x-script.perl-module": "pm", + "text/x-script.phyton": "py", + "text/x-script.rexx": "rexx", + "text/x-script.scheme": "scm", + "text/x-script.sh": "sh", + "text/x-script.tcl": "tcl", + "text/x-script.tcsh": "tcsh", + "text/x-script.zsh": "zsh", + "text/x-server-parsed-html": ["shtml", "ssi"], + "text/x-setext": "etx", + "text/x-sgml": ["sgm", "sgml"], + "text/x-speech": ["spc", "talk"], + "text/x-uil": "uil", + "text/x-uuencode": ["uu", "uue"], + "text/x-vcalendar": "vcs", + "text/x-vcard": "vcf", + "text/xml": "xml", + "video/3gpp": "3gp", + "video/3gpp2": "3g2", + "video/animaflex": "afl", + "video/avi": "avi", + "video/avs-video": "avs", + "video/dl": "dl", + "video/fli": "fli", + "video/gl": "gl", + "video/h261": "h261", + "video/h263": "h263", + "video/h264": "h264", + "video/jpeg": "jpgv", + "video/jpm": "jpm", + "video/mj2": "mj2", + "video/mp4": "mp4", + "video/mpeg": ["mpeg", "mp2", "mpa", "mpe", "mpg", "mpv2", "m1v", "m2v", "mp3"], + "video/msvideo": "avi", + "video/ogg": "ogv", + "video/quicktime": ["mov", "qt", "moov"], + "video/vdo": "vdo", + "video/vivo": ["viv", "vivo"], + "video/vnd.dece.hd": "uvh", + "video/vnd.dece.mobile": "uvm", + "video/vnd.dece.pd": "uvp", + "video/vnd.dece.sd": "uvs", + "video/vnd.dece.video": "uvv", + "video/vnd.fvt": "fvt", + "video/vnd.mpegurl": "mxu", + "video/vnd.ms-playready.media.pyv": "pyv", + "video/vnd.rn-realvideo": "rv", + "video/vnd.uvvu.mp4": "uvu", + "video/vnd.vivo": ["viv", "vivo"], + "video/vosaic": "vos", + "video/webm": "webm", + "video/x-amt-demorun": "xdr", + "video/x-amt-showrun": "xsr", + "video/x-atomic3d-feature": "fmf", + "video/x-dl": "dl", + "video/x-dv": ["dif", "dv"], + "video/x-f4v": "f4v", + "video/x-fli": "fli", + "video/x-flv": "flv", + "video/x-gl": "gl", + "video/x-isvideo": "isu", + "video/x-la-asf": ["lsf", "lsx"], + "video/x-m4v": "m4v", + "video/x-motion-jpeg": "mjpg", + "video/x-mpeg": ["mp3", "mp2"], + "video/x-mpeq2a": "mp2", + "video/x-ms-asf": ["asf", "asr", "asx"], + "video/x-ms-asf-plugin": "asx", + "video/x-ms-wm": "wm", + "video/x-ms-wmv": "wmv", + "video/x-ms-wmx": "wmx", + "video/x-ms-wvx": "wvx", + "video/x-msvideo": "avi", + "video/x-qtc": "qtc", + "video/x-scm": "scm", + "video/x-sgi-movie": ["movie", "mv"], + "windows/metafile": "wmf", + "www/mime": "mime", + "x-conference/x-cooltalk": "ice", + "x-music/x-midi": ["mid", "midi"], + "x-world/x-3dmf": ["3dm", "3dmf", "qd3", "qd3d"], + "x-world/x-svr": "svr", + "x-world/x-vrml": ["flr", "vrml", "wrl", "wrz", "xaf", "xof"], + "x-world/x-vrt": "vrt", + "xgl/drawing": "xgz", + "xgl/movie": "xmz" + }, + extensions: { + "*": "application/octet-stream", + "123": "application/vnd.lotus-1-2-3", + "323": "text/h323", + "3dm": "x-world/x-3dmf", + "3dmf": "x-world/x-3dmf", + "3dml": "text/vnd.in3d.3dml", + "3g2": "video/3gpp2", + "3gp": "video/3gpp", + "7z": "application/x-7z-compressed", + a: "application/octet-stream", + aab: "application/x-authorware-bin", + aac: "audio/x-aac", + aam: "application/x-authorware-map", + aas: "application/x-authorware-seg", + abc: "text/vnd.abc", + abw: "application/x-abiword", + ac: "application/pkix-attr-cert", + acc: "application/vnd.americandynamics.acc", + ace: "application/x-ace-compressed", + acgi: "text/html", + acu: "application/vnd.acucobol", + acx: "application/internet-property-stream", + adp: "audio/adpcm", + aep: "application/vnd.audiograph", + afl: "video/animaflex", + afp: "application/vnd.ibm.modcap", + ahead: "application/vnd.ahead.space", + ai: "application/postscript", + aif: ["audio/aiff", "audio/x-aiff"], + aifc: ["audio/aiff", "audio/x-aiff"], + aiff: ["audio/aiff", "audio/x-aiff"], + aim: "application/x-aim", + aip: "text/x-audiosoft-intra", + air: "application/vnd.adobe.air-application-installer-package+zip", + ait: "application/vnd.dvb.ait", + ami: "application/vnd.amiga.ami", + ani: "application/x-navi-animation", + aos: "application/x-nokia-9000-communicator-add-on-software", + apk: "application/vnd.android.package-archive", + application: "application/x-ms-application", + apr: "application/vnd.lotus-approach", + aps: "application/mime", + arc: "application/octet-stream", + arj: ["application/arj", "application/octet-stream"], + art: "image/x-jg", + asf: "video/x-ms-asf", + asm: "text/x-asm", + aso: "application/vnd.accpac.simply.aso", + asp: "text/asp", + asr: "video/x-ms-asf", + asx: ["video/x-ms-asf", "application/x-mplayer2", "video/x-ms-asf-plugin"], + atc: "application/vnd.acucorp", + atomcat: "application/atomcat+xml", + atomsvc: "application/atomsvc+xml", + atx: "application/vnd.antix.game-component", + au: ["audio/basic", "audio/x-au"], + avi: ["video/avi", "video/msvideo", "application/x-troff-msvideo", "video/x-msvideo"], + avs: "video/avs-video", + aw: "application/applixware", + axs: "application/olescript", + azf: "application/vnd.airzip.filesecure.azf", + azs: "application/vnd.airzip.filesecure.azs", + azw: "application/vnd.amazon.ebook", + bas: "text/plain", + bcpio: "application/x-bcpio", + bdf: "application/x-font-bdf", + bdm: "application/vnd.syncml.dm+wbxml", + bed: "application/vnd.realvnc.bed", + bh2: "application/vnd.fujitsu.oasysprs", + bin: ["application/octet-stream", "application/mac-binary", "application/macbinary", "application/x-macbinary", "application/x-binary"], + bm: "image/bmp", + bmi: "application/vnd.bmi", + bmp: ["image/bmp", "image/x-windows-bmp"], + boo: "application/book", + book: "application/book", + box: "application/vnd.previewsystems.box", + boz: "application/x-bzip2", + bsh: "application/x-bsh", + btif: "image/prs.btif", + bz: "application/x-bzip", + bz2: "application/x-bzip2", + c: ["text/plain", "text/x-c"], + "c++": "text/plain", + c11amc: "application/vnd.cluetrust.cartomobile-config", + c11amz: "application/vnd.cluetrust.cartomobile-config-pkg", + c4g: "application/vnd.clonk.c4group", + cab: "application/vnd.ms-cab-compressed", + car: "application/vnd.curl.car", + cat: ["application/vnd.ms-pkiseccat", "application/vnd.ms-pki.seccat"], + cc: ["text/plain", "text/x-c"], + ccad: "application/clariscad", + cco: "application/x-cocoa", + ccxml: "application/ccxml+xml,", + cdbcmsg: "application/vnd.contact.cmsg", + cdf: ["application/cdf", "application/x-cdf", "application/x-netcdf"], + cdkey: "application/vnd.mediastation.cdkey", + cdmia: "application/cdmi-capability", + cdmic: "application/cdmi-container", + cdmid: "application/cdmi-domain", + cdmio: "application/cdmi-object", + cdmiq: "application/cdmi-queue", + cdx: "chemical/x-cdx", + cdxml: "application/vnd.chemdraw+xml", + cdy: "application/vnd.cinderella", + cer: ["application/pkix-cert", "application/x-x509-ca-cert"], + cgm: "image/cgm", + cha: "application/x-chat", + chat: "application/x-chat", + chm: "application/vnd.ms-htmlhelp", + chrt: "application/vnd.kde.kchart", + cif: "chemical/x-cif", + cii: "application/vnd.anser-web-certificate-issue-initiation", + cil: "application/vnd.ms-artgalry", + cla: "application/vnd.claymore", + class: ["application/octet-stream", "application/java", "application/java-byte-code", "application/java-vm", "application/x-java-class"], + clkk: "application/vnd.crick.clicker.keyboard", + clkp: "application/vnd.crick.clicker.palette", + clkt: "application/vnd.crick.clicker.template", + clkw: "application/vnd.crick.clicker.wordbank", + clkx: "application/vnd.crick.clicker", + clp: "application/x-msclip", + cmc: "application/vnd.cosmocaller", + cmdf: "chemical/x-cmdf", + cml: "chemical/x-cml", + cmp: "application/vnd.yellowriver-custom-menu", + cmx: "image/x-cmx", + cod: ["image/cis-cod", "application/vnd.rim.cod"], + com: ["application/octet-stream", "text/plain"], + conf: "text/plain", + cpio: "application/x-cpio", + cpp: "text/x-c", + cpt: ["application/mac-compactpro", "application/x-compactpro", "application/x-cpt"], + crd: "application/x-mscardfile", + crl: ["application/pkix-crl", "application/pkcs-crl"], + crt: ["application/pkix-cert", "application/x-x509-user-cert", "application/x-x509-ca-cert"], + cryptonote: "application/vnd.rig.cryptonote", + csh: ["text/x-script.csh", "application/x-csh"], + csml: "chemical/x-csml", + csp: "application/vnd.commonspace", + css: ["text/css", "application/x-pointplus"], + csv: "text/csv", + cu: "application/cu-seeme", + curl: "text/vnd.curl", + cww: "application/prs.cww", + cxx: "text/plain", + dae: "model/vnd.collada+xml", + daf: "application/vnd.mobius.daf", + davmount: "application/davmount+xml", + dcr: "application/x-director", + dcurl: "text/vnd.curl.dcurl", + dd2: "application/vnd.oma.dd2+xml", + ddd: "application/vnd.fujixerox.ddd", + deb: "application/x-debian-package", + deepv: "application/x-deepv", + def: "text/plain", + der: "application/x-x509-ca-cert", + dfac: "application/vnd.dreamfactory", + dif: "video/x-dv", + dir: "application/x-director", + dis: "application/vnd.mobius.dis", + djvu: "image/vnd.djvu", + dl: ["video/dl", "video/x-dl"], + dll: "application/x-msdownload", + dms: "application/octet-stream", + dna: "application/vnd.dna", + doc: "application/msword", + docm: "application/vnd.ms-word.document.macroenabled.12", + docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + dot: "application/msword", + dotm: "application/vnd.ms-word.template.macroenabled.12", + dotx: "application/vnd.openxmlformats-officedocument.wordprocessingml.template", + dp: ["application/commonground", "application/vnd.osgi.dp"], + dpg: "application/vnd.dpgraph", + dra: "audio/vnd.dra", + drw: "application/drafting", + dsc: "text/prs.lines.tag", + dssc: "application/dssc+der", + dtb: "application/x-dtbook+xml", + dtd: "application/xml-dtd", + dts: "audio/vnd.dts", + dtshd: "audio/vnd.dts.hd", + dump: "application/octet-stream", + dv: "video/x-dv", + dvi: "application/x-dvi", + dwf: ["model/vnd.dwf", "drawing/x-dwf"], + dwg: ["application/acad", "image/vnd.dwg", "image/x-dwg"], + dxf: ["application/dxf", "image/vnd.dwg", "image/vnd.dxf", "image/x-dwg"], + dxp: "application/vnd.spotfire.dxp", + dxr: "application/x-director", + ecelp4800: "audio/vnd.nuera.ecelp4800", + ecelp7470: "audio/vnd.nuera.ecelp7470", + ecelp9600: "audio/vnd.nuera.ecelp9600", + edm: "application/vnd.novadigm.edm", + edx: "application/vnd.novadigm.edx", + efif: "application/vnd.picsel", + ei6: "application/vnd.pg.osasli", + el: "text/x-script.elisp", + elc: ["application/x-elc", "application/x-bytecode.elisp"], + eml: "message/rfc822", + emma: "application/emma+xml", + env: "application/x-envoy", + eol: "audio/vnd.digital-winds", + eot: "application/vnd.ms-fontobject", + eps: "application/postscript", + epub: "application/epub+zip", + es: ["application/ecmascript", "application/x-esrehber"], + es3: "application/vnd.eszigno3+xml", + esf: "application/vnd.epson.esf", + etx: "text/x-setext", + evy: ["application/envoy", "application/x-envoy"], + exe: ["application/octet-stream", "application/x-msdownload"], + exi: "application/exi", + ext: "application/vnd.novadigm.ext", + ez2: "application/vnd.ezpix-album", + ez3: "application/vnd.ezpix-package", + f: ["text/plain", "text/x-fortran"], + f4v: "video/x-f4v", + f77: "text/x-fortran", + f90: ["text/plain", "text/x-fortran"], + fbs: "image/vnd.fastbidsheet", + fcs: "application/vnd.isac.fcs", + fdf: "application/vnd.fdf", + fe_launch: "application/vnd.denovo.fcselayout-link", + fg5: "application/vnd.fujitsu.oasysgp", + fh: "image/x-freehand", + fif: ["application/fractals", "image/fif"], + fig: "application/x-xfig", + fli: ["video/fli", "video/x-fli"], + flo: ["image/florian", "application/vnd.micrografx.flo"], + flr: "x-world/x-vrml", + flv: "video/x-flv", + flw: "application/vnd.kde.kivio", + flx: "text/vnd.fmi.flexstor", + fly: "text/vnd.fly", + fm: "application/vnd.framemaker", + fmf: "video/x-atomic3d-feature", + fnc: "application/vnd.frogans.fnc", + for: ["text/plain", "text/x-fortran"], + fpx: ["image/vnd.fpx", "image/vnd.net-fpx"], + frl: "application/freeloader", + fsc: "application/vnd.fsc.weblaunch", + fst: "image/vnd.fst", + ftc: "application/vnd.fluxtime.clip", + fti: "application/vnd.anser-web-funds-transfer-initiation", + funk: "audio/make", + fvt: "video/vnd.fvt", + fxp: "application/vnd.adobe.fxp", + fzs: "application/vnd.fuzzysheet", + g: "text/plain", + g2w: "application/vnd.geoplan", + g3: "image/g3fax", + g3w: "application/vnd.geospace", + gac: "application/vnd.groove-account", + gdl: "model/vnd.gdl", + geo: "application/vnd.dynageo", + gex: "application/vnd.geometry-explorer", + ggb: "application/vnd.geogebra.file", + ggt: "application/vnd.geogebra.tool", + ghf: "application/vnd.groove-help", + gif: "image/gif", + gim: "application/vnd.groove-identity-message", + gl: ["video/gl", "video/x-gl"], + gmx: "application/vnd.gmx", + gnumeric: "application/x-gnumeric", + gph: "application/vnd.flographit", + gqf: "application/vnd.grafeq", + gram: "application/srgs", + grv: "application/vnd.groove-injector", + grxml: "application/srgs+xml", + gsd: "audio/x-gsm", + gsf: "application/x-font-ghostscript", + gsm: "audio/x-gsm", + gsp: "application/x-gsp", + gss: "application/x-gss", + gtar: "application/x-gtar", + gtm: "application/vnd.groove-tool-message", + gtw: "model/vnd.gtw", + gv: "text/vnd.graphviz", + gxt: "application/vnd.geonext", + gz: ["application/x-gzip", "application/x-compressed"], + gzip: ["multipart/x-gzip", "application/x-gzip"], + h: ["text/plain", "text/x-h"], + h261: "video/h261", + h263: "video/h263", + h264: "video/h264", + hal: "application/vnd.hal+xml", + hbci: "application/vnd.hbci", + hdf: "application/x-hdf", + help: "application/x-helpfile", + hgl: "application/vnd.hp-hpgl", + hh: ["text/plain", "text/x-h"], + hlb: "text/x-script", + hlp: ["application/winhlp", "application/hlp", "application/x-helpfile", "application/x-winhelp"], + hpg: "application/vnd.hp-hpgl", + hpgl: "application/vnd.hp-hpgl", + hpid: "application/vnd.hp-hpid", + hps: "application/vnd.hp-hps", + hqx: [ + "application/mac-binhex40", + "application/binhex", + "application/binhex4", + "application/mac-binhex", + "application/x-binhex40", + "application/x-mac-binhex40" + ], + hta: "application/hta", + htc: "text/x-component", + htke: "application/vnd.kenameaapp", + htm: "text/html", + html: "text/html", + htmls: "text/html", + htt: "text/webviewhtml", + htx: "text/html", + hvd: "application/vnd.yamaha.hv-dic", + hvp: "application/vnd.yamaha.hv-voice", + hvs: "application/vnd.yamaha.hv-script", + i2g: "application/vnd.intergeo", + icc: "application/vnd.iccprofile", + ice: "x-conference/x-cooltalk", + ico: "image/x-icon", + ics: "text/calendar", + idc: "text/plain", + ief: "image/ief", + iefs: "image/ief", + ifm: "application/vnd.shana.informed.formdata", + iges: ["application/iges", "model/iges"], + igl: "application/vnd.igloader", + igm: "application/vnd.insors.igm", + igs: ["application/iges", "model/iges"], + igx: "application/vnd.micrografx.igx", + iif: "application/vnd.shana.informed.interchange", + iii: "application/x-iphone", + ima: "application/x-ima", + imap: "application/x-httpd-imap", + imp: "application/vnd.accpac.simply.imp", + ims: "application/vnd.ms-ims", + inf: "application/inf", + ins: ["application/x-internet-signup", "application/x-internett-signup"], + ip: "application/x-ip2", + ipfix: "application/ipfix", + ipk: "application/vnd.shana.informed.package", + irm: "application/vnd.ibm.rights-management", + irp: "application/vnd.irepository.package+xml", + isp: "application/x-internet-signup", + isu: "video/x-isvideo", + it: "audio/it", + itp: "application/vnd.shana.informed.formtemplate", + iv: "application/x-inventor", + ivp: "application/vnd.immervision-ivp", + ivr: "i-world/i-vrml", + ivu: "application/vnd.immervision-ivu", + ivy: "application/x-livescreen", + jad: "text/vnd.sun.j2me.app-descriptor", + jam: ["application/vnd.jam", "audio/x-jam"], + jar: "application/java-archive", + jav: ["text/plain", "text/x-java-source"], + java: ["text/plain", "text/x-java-source,java", "text/x-java-source"], + jcm: "application/x-java-commerce", + jfif: ["image/pipeg", "image/jpeg", "image/pjpeg"], + "jfif-tbnl": "image/jpeg", + jisp: "application/vnd.jisp", + jlt: "application/vnd.hp-jlyt", + jnlp: "application/x-java-jnlp-file", + joda: "application/vnd.joost.joda-archive", + jpe: ["image/jpeg", "image/pjpeg"], + jpeg: ["image/jpeg", "image/pjpeg"], + jpg: ["image/jpeg", "image/pjpeg"], + jpgv: "video/jpeg", + jpm: "video/jpm", + jps: "image/x-jps", + js: ["application/javascript", "application/ecmascript", "text/javascript", "text/ecmascript", "application/x-javascript"], + json: "application/json", + jut: "image/jutvision", + kar: ["audio/midi", "music/x-karaoke"], + karbon: "application/vnd.kde.karbon", + kfo: "application/vnd.kde.kformula", + kia: "application/vnd.kidspiration", + kml: "application/vnd.google-earth.kml+xml", + kmz: "application/vnd.google-earth.kmz", + kne: "application/vnd.kinar", + kon: "application/vnd.kde.kontour", + kpr: "application/vnd.kde.kpresenter", + ksh: ["application/x-ksh", "text/x-script.ksh"], + ksp: "application/vnd.kde.kspread", + ktx: "image/ktx", + ktz: "application/vnd.kahootz", + kwd: "application/vnd.kde.kword", + la: ["audio/nspaudio", "audio/x-nspaudio"], + lam: "audio/x-liveaudio", + lasxml: "application/vnd.las.las+xml", + latex: "application/x-latex", + lbd: "application/vnd.llamagraphics.life-balance.desktop", + lbe: "application/vnd.llamagraphics.life-balance.exchange+xml", + les: "application/vnd.hhe.lesson-player", + lha: ["application/octet-stream", "application/lha", "application/x-lha"], + lhx: "application/octet-stream", + link66: "application/vnd.route66.link66+xml", + list: "text/plain", + lma: ["audio/nspaudio", "audio/x-nspaudio"], + log: "text/plain", + lrm: "application/vnd.ms-lrm", + lsf: "video/x-la-asf", + lsp: ["application/x-lisp", "text/x-script.lisp"], + lst: "text/plain", + lsx: ["video/x-la-asf", "text/x-la-asf"], + ltf: "application/vnd.frogans.ltf", + ltx: "application/x-latex", + lvp: "audio/vnd.lucent.voice", + lwp: "application/vnd.lotus-wordpro", + lzh: ["application/octet-stream", "application/x-lzh"], + lzx: ["application/lzx", "application/octet-stream", "application/x-lzx"], + m: ["text/plain", "text/x-m"], + m13: "application/x-msmediaview", + m14: "application/x-msmediaview", + m1v: "video/mpeg", + m21: "application/mp21", + m2a: "audio/mpeg", + m2v: "video/mpeg", + m3u: ["audio/x-mpegurl", "audio/x-mpequrl"], + m3u8: "application/vnd.apple.mpegurl", + m4v: "video/x-m4v", + ma: "application/mathematica", + mads: "application/mads+xml", + mag: "application/vnd.ecowin.chart", + man: "application/x-troff-man", + map: "application/x-navimap", + mar: "text/plain", + mathml: "application/mathml+xml", + mbd: "application/mbedlet", + mbk: "application/vnd.mobius.mbk", + mbox: "application/mbox", + mc$: "application/x-magic-cap-package-1.0", + mc1: "application/vnd.medcalcdata", + mcd: ["application/mcad", "application/vnd.mcd", "application/x-mathcad"], + mcf: ["image/vasa", "text/mcf"], + mcp: "application/netmc", + mcurl: "text/vnd.curl.mcurl", + mdb: "application/x-msaccess", + mdi: "image/vnd.ms-modi", + me: "application/x-troff-me", + meta4: "application/metalink4+xml", + mets: "application/mets+xml", + mfm: "application/vnd.mfmp", + mgp: "application/vnd.osgeo.mapguide.package", + mgz: "application/vnd.proteus.magazine", + mht: "message/rfc822", + mhtml: "message/rfc822", + mid: ["audio/mid", "audio/midi", "music/crescendo", "x-music/x-midi", "audio/x-midi", "application/x-midi", "audio/x-mid"], + midi: ["audio/midi", "music/crescendo", "x-music/x-midi", "audio/x-midi", "application/x-midi", "audio/x-mid"], + mif: ["application/vnd.mif", "application/x-mif", "application/x-frame"], + mime: ["message/rfc822", "www/mime"], + mj2: "video/mj2", + mjf: "audio/x-vnd.audioexplosion.mjuicemediafile", + mjpg: "video/x-motion-jpeg", + mlp: "application/vnd.dolby.mlp", + mm: ["application/base64", "application/x-meme"], + mmd: "application/vnd.chipnuts.karaoke-mmd", + mme: "application/base64", + mmf: "application/vnd.smaf", + mmr: "image/vnd.fujixerox.edmics-mmr", + mny: "application/x-msmoney", + mod: ["audio/mod", "audio/x-mod"], + mods: "application/mods+xml", + moov: "video/quicktime", + mov: "video/quicktime", + movie: "video/x-sgi-movie", + mp2: ["video/mpeg", "audio/mpeg", "video/x-mpeg", "audio/x-mpeg", "video/x-mpeq2a"], + mp3: ["audio/mpeg", "audio/mpeg3", "video/mpeg", "audio/x-mpeg-3", "video/x-mpeg"], + mp4: ["video/mp4", "application/mp4"], + mp4a: "audio/mp4", + mpa: ["video/mpeg", "audio/mpeg"], + mpc: ["application/vnd.mophun.certificate", "application/x-project"], + mpe: "video/mpeg", + mpeg: "video/mpeg", + mpg: ["video/mpeg", "audio/mpeg"], + mpga: "audio/mpeg", + mpkg: "application/vnd.apple.installer+xml", + mpm: "application/vnd.blueice.multipass", + mpn: "application/vnd.mophun.application", + mpp: "application/vnd.ms-project", + mpt: "application/x-project", + mpv: "application/x-project", + mpv2: "video/mpeg", + mpx: "application/x-project", + mpy: "application/vnd.ibm.minipay", + mqy: "application/vnd.mobius.mqy", + mrc: "application/marc", + mrcx: "application/marcxml+xml", + ms: "application/x-troff-ms", + mscml: "application/mediaservercontrol+xml", + mseq: "application/vnd.mseq", + msf: "application/vnd.epson.msf", + msg: "application/vnd.ms-outlook", + msh: "model/mesh", + msl: "application/vnd.mobius.msl", + msty: "application/vnd.muvee.style", + mts: "model/vnd.mts", + mus: "application/vnd.musician", + musicxml: "application/vnd.recordare.musicxml+xml", + mv: "video/x-sgi-movie", + mvb: "application/x-msmediaview", + mwf: "application/vnd.mfer", + mxf: "application/mxf", + mxl: "application/vnd.recordare.musicxml", + mxml: "application/xv+xml", + mxs: "application/vnd.triscape.mxs", + mxu: "video/vnd.mpegurl", + my: "audio/make", + mzz: "application/x-vnd.audioexplosion.mzz", + "n-gage": "application/vnd.nokia.n-gage.symbian.install", + n3: "text/n3", + nap: "image/naplps", + naplps: "image/naplps", + nbp: "application/vnd.wolfram.player", + nc: "application/x-netcdf", + ncm: "application/vnd.nokia.configuration-message", + ncx: "application/x-dtbncx+xml", + ngdat: "application/vnd.nokia.n-gage.data", + nif: "image/x-niff", + niff: "image/x-niff", + nix: "application/x-mix-transfer", + nlu: "application/vnd.neurolanguage.nlu", + nml: "application/vnd.enliven", + nnd: "application/vnd.noblenet-directory", + nns: "application/vnd.noblenet-sealer", + nnw: "application/vnd.noblenet-web", + npx: "image/vnd.net-fpx", + nsc: "application/x-conference", + nsf: "application/vnd.lotus-notes", + nvd: "application/x-navidoc", + nws: "message/rfc822", + o: "application/octet-stream", + oa2: "application/vnd.fujitsu.oasys2", + oa3: "application/vnd.fujitsu.oasys3", + oas: "application/vnd.fujitsu.oasys", + obd: "application/x-msbinder", + oda: "application/oda", + odb: "application/vnd.oasis.opendocument.database", + odc: "application/vnd.oasis.opendocument.chart", + odf: "application/vnd.oasis.opendocument.formula", + odft: "application/vnd.oasis.opendocument.formula-template", + odg: "application/vnd.oasis.opendocument.graphics", + odi: "application/vnd.oasis.opendocument.image", + odm: "application/vnd.oasis.opendocument.text-master", + odp: "application/vnd.oasis.opendocument.presentation", + ods: "application/vnd.oasis.opendocument.spreadsheet", + odt: "application/vnd.oasis.opendocument.text", + oga: "audio/ogg", + ogv: "video/ogg", + ogx: "application/ogg", + omc: "application/x-omc", + omcd: "application/x-omcdatamaker", + omcr: "application/x-omcregerator", + onetoc: "application/onenote", + opf: "application/oebps-package+xml", + org: "application/vnd.lotus-organizer", + osf: "application/vnd.yamaha.openscoreformat", + osfpvg: "application/vnd.yamaha.openscoreformat.osfpvg+xml", + otc: "application/vnd.oasis.opendocument.chart-template", + otf: "application/x-font-otf", + otg: "application/vnd.oasis.opendocument.graphics-template", + oth: "application/vnd.oasis.opendocument.text-web", + oti: "application/vnd.oasis.opendocument.image-template", + otp: "application/vnd.oasis.opendocument.presentation-template", + ots: "application/vnd.oasis.opendocument.spreadsheet-template", + ott: "application/vnd.oasis.opendocument.text-template", + oxt: "application/vnd.openofficeorg.extension", + p: "text/x-pascal", + p10: ["application/pkcs10", "application/x-pkcs10"], + p12: ["application/pkcs-12", "application/x-pkcs12"], + p7a: "application/x-pkcs7-signature", + p7b: "application/x-pkcs7-certificates", + p7c: ["application/pkcs7-mime", "application/x-pkcs7-mime"], + p7m: ["application/pkcs7-mime", "application/x-pkcs7-mime"], + p7r: "application/x-pkcs7-certreqresp", + p7s: ["application/pkcs7-signature", "application/x-pkcs7-signature"], + p8: "application/pkcs8", + par: "text/plain-bas", + part: "application/pro_eng", + pas: "text/pascal", + paw: "application/vnd.pawaafile", + pbd: "application/vnd.powerbuilder6", + pbm: "image/x-portable-bitmap", + pcf: "application/x-font-pcf", + pcl: ["application/vnd.hp-pcl", "application/x-pcl"], + pclxl: "application/vnd.hp-pclxl", + pct: "image/x-pict", + pcurl: "application/vnd.curl.pcurl", + pcx: "image/x-pcx", + pdb: ["application/vnd.palm", "chemical/x-pdb"], + pdf: "application/pdf", + pfa: "application/x-font-type1", + pfr: "application/font-tdpfr", + pfunk: ["audio/make", "audio/make.my.funk"], + pfx: "application/x-pkcs12", + pgm: ["image/x-portable-graymap", "image/x-portable-greymap"], + pgn: "application/x-chess-pgn", + pgp: "application/pgp-signature", + pic: ["image/pict", "image/x-pict"], + pict: "image/pict", + pkg: "application/x-newton-compatible-pkg", + pki: "application/pkixcmp", + pkipath: "application/pkix-pkipath", + pko: ["application/ynd.ms-pkipko", "application/vnd.ms-pki.pko"], + pl: ["text/plain", "text/x-script.perl"], + plb: "application/vnd.3gpp.pic-bw-large", + plc: "application/vnd.mobius.plc", + plf: "application/vnd.pocketlearn", + pls: "application/pls+xml", + plx: "application/x-pixclscript", + pm: ["text/x-script.perl-module", "image/x-xpixmap"], + pm4: "application/x-pagemaker", + pm5: "application/x-pagemaker", + pma: "application/x-perfmon", + pmc: "application/x-perfmon", + pml: ["application/vnd.ctc-posml", "application/x-perfmon"], + pmr: "application/x-perfmon", + pmw: "application/x-perfmon", + png: "image/png", + pnm: ["application/x-portable-anymap", "image/x-portable-anymap"], + portpkg: "application/vnd.macports.portpkg", + pot: ["application/vnd.ms-powerpoint", "application/mspowerpoint"], + potm: "application/vnd.ms-powerpoint.template.macroenabled.12", + potx: "application/vnd.openxmlformats-officedocument.presentationml.template", + pov: "model/x-pov", + ppa: "application/vnd.ms-powerpoint", + ppam: "application/vnd.ms-powerpoint.addin.macroenabled.12", + ppd: "application/vnd.cups-ppd", + ppm: "image/x-portable-pixmap", + pps: ["application/vnd.ms-powerpoint", "application/mspowerpoint"], + ppsm: "application/vnd.ms-powerpoint.slideshow.macroenabled.12", + ppsx: "application/vnd.openxmlformats-officedocument.presentationml.slideshow", + ppt: ["application/vnd.ms-powerpoint", "application/mspowerpoint", "application/powerpoint", "application/x-mspowerpoint"], + pptm: "application/vnd.ms-powerpoint.presentation.macroenabled.12", + pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation", + ppz: "application/mspowerpoint", + prc: "application/x-mobipocket-ebook", + pre: ["application/vnd.lotus-freelance", "application/x-freelance"], + prf: "application/pics-rules", + prt: "application/pro_eng", + ps: "application/postscript", + psb: "application/vnd.3gpp.pic-bw-small", + psd: ["application/octet-stream", "image/vnd.adobe.photoshop"], + psf: "application/x-font-linux-psf", + pskcxml: "application/pskc+xml", + ptid: "application/vnd.pvi.ptid1", + pub: "application/x-mspublisher", + pvb: "application/vnd.3gpp.pic-bw-var", + pvu: "paleovu/x-pv", + pwn: "application/vnd.3m.post-it-notes", + pwz: "application/vnd.ms-powerpoint", + py: "text/x-script.phyton", + pya: "audio/vnd.ms-playready.media.pya", + pyc: "applicaiton/x-bytecode.python", + pyv: "video/vnd.ms-playready.media.pyv", + qam: "application/vnd.epson.quickanime", + qbo: "application/vnd.intu.qbo", + qcp: "audio/vnd.qcelp", + qd3: "x-world/x-3dmf", + qd3d: "x-world/x-3dmf", + qfx: "application/vnd.intu.qfx", + qif: "image/x-quicktime", + qps: "application/vnd.publishare-delta-tree", + qt: "video/quicktime", + qtc: "video/x-qtc", + qti: "image/x-quicktime", + qtif: "image/x-quicktime", + qxd: "application/vnd.quark.quarkxpress", + ra: ["audio/x-realaudio", "audio/x-pn-realaudio", "audio/x-pn-realaudio-plugin"], + ram: "audio/x-pn-realaudio", + rar: "application/x-rar-compressed", + ras: ["image/cmu-raster", "application/x-cmu-raster", "image/x-cmu-raster"], + rast: "image/cmu-raster", + rcprofile: "application/vnd.ipunplugged.rcprofile", + rdf: "application/rdf+xml", + rdz: "application/vnd.data-vision.rdz", + rep: "application/vnd.businessobjects", + res: "application/x-dtbresource+xml", + rexx: "text/x-script.rexx", + rf: "image/vnd.rn-realflash", + rgb: "image/x-rgb", + rif: "application/reginfo+xml", + rip: "audio/vnd.rip", + rl: "application/resource-lists+xml", + rlc: "image/vnd.fujixerox.edmics-rlc", + rld: "application/resource-lists-diff+xml", + rm: ["application/vnd.rn-realmedia", "audio/x-pn-realaudio"], + rmi: "audio/mid", + rmm: "audio/x-pn-realaudio", + rmp: ["audio/x-pn-realaudio-plugin", "audio/x-pn-realaudio"], + rms: "application/vnd.jcp.javame.midlet-rms", + rnc: "application/relax-ng-compact-syntax", + rng: ["application/ringing-tones", "application/vnd.nokia.ringing-tone"], + rnx: "application/vnd.rn-realplayer", + roff: "application/x-troff", + rp: "image/vnd.rn-realpix", + rp9: "application/vnd.cloanto.rp9", + rpm: "audio/x-pn-realaudio-plugin", + rpss: "application/vnd.nokia.radio-presets", + rpst: "application/vnd.nokia.radio-preset", + rq: "application/sparql-query", + rs: "application/rls-services+xml", + rsd: "application/rsd+xml", + rt: ["text/richtext", "text/vnd.rn-realtext"], + rtf: ["application/rtf", "text/richtext", "application/x-rtf"], + rtx: ["text/richtext", "application/rtf"], + rv: "video/vnd.rn-realvideo", + s: "text/x-asm", + s3m: "audio/s3m", + saf: "application/vnd.yamaha.smaf-audio", + saveme: "application/octet-stream", + sbk: "application/x-tbook", + sbml: "application/sbml+xml", + sc: "application/vnd.ibm.secure-container", + scd: "application/x-msschedule", + scm: ["application/vnd.lotus-screencam", "video/x-scm", "text/x-script.guile", "application/x-lotusscreencam", "text/x-script.scheme"], + scq: "application/scvp-cv-request", + scs: "application/scvp-cv-response", + sct: "text/scriptlet", + scurl: "text/vnd.curl.scurl", + sda: "application/vnd.stardivision.draw", + sdc: "application/vnd.stardivision.calc", + sdd: "application/vnd.stardivision.impress", + sdkm: "application/vnd.solent.sdkm+xml", + sdml: "text/plain", + sdp: ["application/sdp", "application/x-sdp"], + sdr: "application/sounder", + sdw: "application/vnd.stardivision.writer", + sea: ["application/sea", "application/x-sea"], + see: "application/vnd.seemail", + seed: "application/vnd.fdsn.seed", + sema: "application/vnd.sema", + semd: "application/vnd.semd", + semf: "application/vnd.semf", + ser: "application/java-serialized-object", + set: "application/set", + setpay: "application/set-payment-initiation", + setreg: "application/set-registration-initiation", + "sfd-hdstx": "application/vnd.hydrostatix.sof-data", + sfs: "application/vnd.spotfire.sfs", + sgl: "application/vnd.stardivision.writer-global", + sgm: ["text/sgml", "text/x-sgml"], + sgml: ["text/sgml", "text/x-sgml"], + sh: ["application/x-shar", "application/x-bsh", "application/x-sh", "text/x-script.sh"], + shar: ["application/x-bsh", "application/x-shar"], + shf: "application/shf+xml", + shtml: ["text/html", "text/x-server-parsed-html"], + sid: "audio/x-psid", + sis: "application/vnd.symbian.install", + sit: ["application/x-stuffit", "application/x-sit"], + sitx: "application/x-stuffitx", + skd: "application/x-koan", + skm: "application/x-koan", + skp: ["application/vnd.koan", "application/x-koan"], + skt: "application/x-koan", + sl: "application/x-seelogo", + sldm: "application/vnd.ms-powerpoint.slide.macroenabled.12", + sldx: "application/vnd.openxmlformats-officedocument.presentationml.slide", + slt: "application/vnd.epson.salt", + sm: "application/vnd.stepmania.stepchart", + smf: "application/vnd.stardivision.math", + smi: ["application/smil", "application/smil+xml"], + smil: "application/smil", + snd: ["audio/basic", "audio/x-adpcm"], + snf: "application/x-font-snf", + sol: "application/solids", + spc: ["text/x-speech", "application/x-pkcs7-certificates"], + spf: "application/vnd.yamaha.smaf-phrase", + spl: ["application/futuresplash", "application/x-futuresplash"], + spot: "text/vnd.in3d.spot", + spp: "application/scvp-vp-response", + spq: "application/scvp-vp-request", + spr: "application/x-sprite", + sprite: "application/x-sprite", + src: "application/x-wais-source", + sru: "application/sru+xml", + srx: "application/sparql-results+xml", + sse: "application/vnd.kodak-descriptor", + ssf: "application/vnd.epson.ssf", + ssi: "text/x-server-parsed-html", + ssm: "application/streamingmedia", + ssml: "application/ssml+xml", + sst: ["application/vnd.ms-pkicertstore", "application/vnd.ms-pki.certstore"], + st: "application/vnd.sailingtracker.track", + stc: "application/vnd.sun.xml.calc.template", + std: "application/vnd.sun.xml.draw.template", + step: "application/step", + stf: "application/vnd.wt.stf", + sti: "application/vnd.sun.xml.impress.template", + stk: "application/hyperstudio", + stl: ["application/vnd.ms-pkistl", "application/sla", "application/vnd.ms-pki.stl", "application/x-navistyle"], + stm: "text/html", + stp: "application/step", + str: "application/vnd.pg.format", + stw: "application/vnd.sun.xml.writer.template", + sub: "image/vnd.dvb.subtitle", + sus: "application/vnd.sus-calendar", + sv4cpio: "application/x-sv4cpio", + sv4crc: "application/x-sv4crc", + svc: "application/vnd.dvb.service", + svd: "application/vnd.svd", + svf: ["image/vnd.dwg", "image/x-dwg"], + svg: "image/svg+xml", + svr: ["x-world/x-svr", "application/x-world"], + swf: "application/x-shockwave-flash", + swi: "application/vnd.aristanetworks.swi", + sxc: "application/vnd.sun.xml.calc", + sxd: "application/vnd.sun.xml.draw", + sxg: "application/vnd.sun.xml.writer.global", + sxi: "application/vnd.sun.xml.impress", + sxm: "application/vnd.sun.xml.math", + sxw: "application/vnd.sun.xml.writer", + t: ["text/troff", "application/x-troff"], + talk: "text/x-speech", + tao: "application/vnd.tao.intent-module-archive", + tar: "application/x-tar", + tbk: ["application/toolbook", "application/x-tbook"], + tcap: "application/vnd.3gpp2.tcap", + tcl: ["text/x-script.tcl", "application/x-tcl"], + tcsh: "text/x-script.tcsh", + teacher: "application/vnd.smart.teacher", + tei: "application/tei+xml", + tex: "application/x-tex", + texi: "application/x-texinfo", + texinfo: "application/x-texinfo", + text: ["application/plain", "text/plain"], + tfi: "application/thraud+xml", + tfm: "application/x-tex-tfm", + tgz: ["application/gnutar", "application/x-compressed"], + thmx: "application/vnd.ms-officetheme", + tif: ["image/tiff", "image/x-tiff"], + tiff: ["image/tiff", "image/x-tiff"], + tmo: "application/vnd.tmobile-livetv", + torrent: "application/x-bittorrent", + tpl: "application/vnd.groove-tool-template", + tpt: "application/vnd.trid.tpt", + tr: "application/x-troff", + tra: "application/vnd.trueapp", + trm: "application/x-msterminal", + tsd: "application/timestamped-data", + tsi: "audio/tsp-audio", + tsp: ["application/dsptype", "audio/tsplayer"], + tsv: "text/tab-separated-values", + ttf: "application/x-font-ttf", + ttl: "text/turtle", + turbot: "image/florian", + twd: "application/vnd.simtech-mindmapper", + txd: "application/vnd.genomatix.tuxedo", + txf: "application/vnd.mobius.txf", + txt: "text/plain", + ufd: "application/vnd.ufdl", + uil: "text/x-uil", + uls: "text/iuls", + umj: "application/vnd.umajin", + uni: "text/uri-list", + unis: "text/uri-list", + unityweb: "application/vnd.unity", + unv: "application/i-deas", + uoml: "application/vnd.uoml+xml", + uri: "text/uri-list", + uris: "text/uri-list", + ustar: ["application/x-ustar", "multipart/x-ustar"], + utz: "application/vnd.uiq.theme", + uu: ["application/octet-stream", "text/x-uuencode"], + uue: "text/x-uuencode", + uva: "audio/vnd.dece.audio", + uvh: "video/vnd.dece.hd", + uvi: "image/vnd.dece.graphic", + uvm: "video/vnd.dece.mobile", + uvp: "video/vnd.dece.pd", + uvs: "video/vnd.dece.sd", + uvu: "video/vnd.uvvu.mp4", + uvv: "video/vnd.dece.video", + vcd: "application/x-cdlink", + vcf: "text/x-vcard", + vcg: "application/vnd.groove-vcard", + vcs: "text/x-vcalendar", + vcx: "application/vnd.vcx", + vda: "application/vda", + vdo: "video/vdo", + vew: "application/groupwise", + vis: "application/vnd.visionary", + viv: ["video/vivo", "video/vnd.vivo"], + vivo: ["video/vivo", "video/vnd.vivo"], + vmd: "application/vocaltec-media-desc", + vmf: "application/vocaltec-media-file", + voc: ["audio/voc", "audio/x-voc"], + vos: "video/vosaic", + vox: "audio/voxware", + vqe: "audio/x-twinvq-plugin", + vqf: "audio/x-twinvq", + vql: "audio/x-twinvq-plugin", + vrml: ["model/vrml", "x-world/x-vrml", "application/x-vrml"], + vrt: "x-world/x-vrt", + vsd: ["application/vnd.visio", "application/x-visio"], + vsf: "application/vnd.vsf", + vst: "application/x-visio", + vsw: "application/x-visio", + vtu: "model/vnd.vtu", + vxml: "application/voicexml+xml", + w60: "application/wordperfect6.0", + w61: "application/wordperfect6.1", + w6w: "application/msword", + wad: "application/x-doom", + wav: ["audio/wav", "audio/x-wav"], + wax: "audio/x-ms-wax", + wb1: "application/x-qpro", + wbmp: "image/vnd.wap.wbmp", + wbs: "application/vnd.criticaltools.wbs+xml", + wbxml: "application/vnd.wap.wbxml", + wcm: "application/vnd.ms-works", + wdb: "application/vnd.ms-works", + web: "application/vnd.xara", + weba: "audio/webm", + webm: "video/webm", + webp: "image/webp", + wg: "application/vnd.pmi.widget", + wgt: "application/widget", + wiz: "application/msword", + wk1: "application/x-123", + wks: "application/vnd.ms-works", + wm: "video/x-ms-wm", + wma: "audio/x-ms-wma", + wmd: "application/x-ms-wmd", + wmf: ["windows/metafile", "application/x-msmetafile"], + wml: "text/vnd.wap.wml", + wmlc: "application/vnd.wap.wmlc", + wmls: "text/vnd.wap.wmlscript", + wmlsc: "application/vnd.wap.wmlscriptc", + wmv: "video/x-ms-wmv", + wmx: "video/x-ms-wmx", + wmz: "application/x-ms-wmz", + woff: "application/x-font-woff", + word: "application/msword", + wp: "application/wordperfect", + wp5: ["application/wordperfect", "application/wordperfect6.0"], + wp6: "application/wordperfect", + wpd: ["application/wordperfect", "application/vnd.wordperfect", "application/x-wpwin"], + wpl: "application/vnd.ms-wpl", + wps: "application/vnd.ms-works", + wq1: "application/x-lotus", + wqd: "application/vnd.wqd", + wri: ["application/mswrite", "application/x-wri", "application/x-mswrite"], + wrl: ["model/vrml", "x-world/x-vrml", "application/x-world"], + wrz: ["model/vrml", "x-world/x-vrml"], + wsc: "text/scriplet", + wsdl: "application/wsdl+xml", + wspolicy: "application/wspolicy+xml", + wsrc: "application/x-wais-source", + wtb: "application/vnd.webturbo", + wtk: "application/x-wintalk", + wvx: "video/x-ms-wvx", + "x-png": "image/png", + x3d: "application/vnd.hzn-3d-crossword", + xaf: "x-world/x-vrml", + xap: "application/x-silverlight-app", + xar: "application/vnd.xara", + xbap: "application/x-ms-xbap", + xbd: "application/vnd.fujixerox.docuworks.binder", + xbm: ["image/xbm", "image/x-xbm", "image/x-xbitmap"], + xdf: "application/xcap-diff+xml", + xdm: "application/vnd.syncml.dm+xml", + xdp: "application/vnd.adobe.xdp+xml", + xdr: "video/x-amt-demorun", + xdssc: "application/dssc+xml", + xdw: "application/vnd.fujixerox.docuworks", + xenc: "application/xenc+xml", + xer: "application/patch-ops-error+xml", + xfdf: "application/vnd.adobe.xfdf", + xfdl: "application/vnd.xfdl", + xgz: "xgl/drawing", + xhtml: "application/xhtml+xml", + xif: "image/vnd.xiff", + xl: "application/excel", + xla: ["application/vnd.ms-excel", "application/excel", "application/x-msexcel", "application/x-excel"], + xlam: "application/vnd.ms-excel.addin.macroenabled.12", + xlb: ["application/excel", "application/vnd.ms-excel", "application/x-excel"], + xlc: ["application/vnd.ms-excel", "application/excel", "application/x-excel"], + xld: ["application/excel", "application/x-excel"], + xlk: ["application/excel", "application/x-excel"], + xll: ["application/excel", "application/vnd.ms-excel", "application/x-excel"], + xlm: ["application/vnd.ms-excel", "application/excel", "application/x-excel"], + xls: ["application/vnd.ms-excel", "application/excel", "application/x-msexcel", "application/x-excel"], + xlsb: "application/vnd.ms-excel.sheet.binary.macroenabled.12", + xlsm: "application/vnd.ms-excel.sheet.macroenabled.12", + xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + xlt: ["application/vnd.ms-excel", "application/excel", "application/x-excel"], + xltm: "application/vnd.ms-excel.template.macroenabled.12", + xltx: "application/vnd.openxmlformats-officedocument.spreadsheetml.template", + xlv: ["application/excel", "application/x-excel"], + xlw: ["application/vnd.ms-excel", "application/excel", "application/x-msexcel", "application/x-excel"], + xm: "audio/xm", + xml: ["application/xml", "text/xml", "application/atom+xml", "application/rss+xml"], + xmz: "xgl/movie", + xo: "application/vnd.olpc-sugar", + xof: "x-world/x-vrml", + xop: "application/xop+xml", + xpi: "application/x-xpinstall", + xpix: "application/x-vnd.ls-xpix", + xpm: ["image/xpm", "image/x-xpixmap"], + xpr: "application/vnd.is-xpr", + xps: "application/vnd.ms-xpsdocument", + xpw: "application/vnd.intercon.formnet", + xslt: "application/xslt+xml", + xsm: "application/vnd.syncml+xml", + xspf: "application/xspf+xml", + xsr: "video/x-amt-showrun", + xul: "application/vnd.mozilla.xul+xml", + xwd: ["image/x-xwd", "image/x-xwindowdump"], + xyz: ["chemical/x-xyz", "chemical/x-pdb"], + yang: "application/yang", + yin: "application/yin+xml", + z: ["application/x-compressed", "application/x-compress"], + zaz: "application/vnd.zzazz.deck+xml", + zip: ["application/zip", "multipart/x-zip", "application/x-zip-compressed", "application/x-compressed"], + zir: "application/vnd.zul", + zmm: "application/vnd.handheld-entertainment+xml", + zoo: "application/octet-stream", + zsh: "text/x-script.zsh" + } + }; + } +}); + +// ../node_modules/libmime/lib/libmime.js +var require_libmime2 = __commonJS({ + "../node_modules/libmime/lib/libmime.js"(exports, module) { + "use strict"; + var { Buffer: Buffer2 } = __require("node:buffer"); + var libcharset = require_charset2(); + var libbase64 = require_libbase64(); + var libqp = require_libqp(); + var mimetypes = require_mimetypes2(); + var STAGE_KEY = 4097; + var STAGE_VALUE = 4098; + var Libmime = class { + constructor(config2) { + this.config = config2 || {}; + } + /** + * Checks if a value is plaintext string (uses only printable 7bit chars) + * + * @param {String} value String to be tested + * @returns {Boolean} true if it is a plaintext string + */ + isPlainText(value) { + if (typeof value !== "string" || /[\x00-\x08\x0b\x0c\x0e-\x1f\u0080-\uFFFF]/.test(value)) { + return false; + } else { + return true; + } + } + /** + * Checks if a multi line string containes lines longer than the selected value. + * + * Useful when detecting if a mail message needs any processing at all – + * if only plaintext characters are used and lines are short, then there is + * no need to encode the values in any way. If the value is plaintext but has + * longer lines then allowed, then use format=flowed + * + * @param {String} str String to be tested + * @param {Number} lineLength Max line length to check for + * @returns {Boolean} Returns true if there is at least one line longer than lineLength chars + */ + hasLongerLines(str, lineLength) { + return new RegExp("^.{" + (lineLength + 1) + ",}", "m").test(str); + } + /** + * Decodes a string from a format=flowed soft wrapping. + * + * @param {String} str Plaintext string with format=flowed to decode + * @param {Boolean} [delSp] If true, delete leading spaces (delsp=yes) + * @return {String} Mime decoded string + */ + decodeFlowed(str, delSp) { + str = (str || "").toString(); + let lines = str.split(/\r?\n/); + let result = [], buffer = null; + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; + let isSoftBreak = buffer !== null && / $/.test(buffer) && !/(^|\n)-- $/.test(buffer); + if (isSoftBreak) { + if (delSp) { + buffer = buffer.slice(0, -1) + line; + } else { + buffer += line; + } + } else { + if (buffer !== null) { + result.push(buffer); + } + buffer = line; + } + } + if (buffer) { + result.push(buffer); + } + return result.join("\n").replace(/^ /gm, ""); + } + /** + * Adds soft line breaks to content marked with format=flowed to + * ensure that no line in the message is never longer than lineLength + * + * @param {String} str Plaintext string that requires wrapping + * @param {Number} [lineLength=76] Maximum length of a line + * @return {String} String with forced line breaks + */ + encodeFlowed(str, lineLength) { + lineLength = lineLength || 76; + let flowed = []; + str.split(/\r?\n/).forEach((line) => { + flowed.push( + this.foldLines( + line.replace(/^( |From|>)/gim, " $1"), + lineLength, + true + ) + ); + }); + return flowed.join("\r\n"); + } + /** + * Encodes a string or an Buffer to an UTF-8 MIME Word (rfc2047) + * + * @param {String|Buffer} data String to be encoded + * @param {String} mimeWordEncoding='Q' Encoding for the mime word, either Q or B + * @param {Number} [maxLength=0] If set, split mime words into several chunks if needed + * @return {String} Single or several mime words joined together + */ + encodeWord(data, mimeWordEncoding, maxLength) { + mimeWordEncoding = (mimeWordEncoding || "Q").toString().toUpperCase().trim().charAt(0); + maxLength = maxLength || 0; + let encodedStr; + let toCharset = "UTF-8"; + if (maxLength && maxLength > 7 + toCharset.length) { + maxLength -= 7 + toCharset.length; + } + if (mimeWordEncoding === "Q") { + encodedStr = libqp.encode(data).replace(/[^a-z0-9!*+\-/=]/gi, (chr) => { + let ord = chr.charCodeAt(0).toString(16).toUpperCase(); + if (chr === " ") { + return "_"; + } else { + return "=" + (ord.length === 1 ? "0" + ord : ord); + } + }); + } else if (mimeWordEncoding === "B") { + encodedStr = typeof data === "string" ? data : libbase64.encode(data); + maxLength = maxLength ? Math.max(3, (maxLength - maxLength % 4) / 4 * 3) : 0; + } + if (maxLength && (mimeWordEncoding !== "B" ? encodedStr : libbase64.encode(data)).length > maxLength) { + if (mimeWordEncoding === "Q") { + encodedStr = this.splitMimeEncodedString(encodedStr, maxLength).join("?= =?" + toCharset + "?" + mimeWordEncoding + "?"); + } else { + let parts = []; + let lpart = ""; + for (let i = 0, len = encodedStr.length; i < len; i++) { + let chr = encodedStr.charAt(i); + if (/[\ud83c\ud83d\ud83e]/.test(chr) && i < len - 1) { + chr += encodedStr.charAt(++i); + } + if (Buffer2.byteLength(lpart + chr) <= maxLength || i === 0) { + lpart += chr; + } else { + parts.push(libbase64.encode(lpart)); + lpart = chr; + } + } + if (lpart) { + parts.push(libbase64.encode(lpart)); + } + if (parts.length > 1) { + encodedStr = parts.join("?= =?" + toCharset + "?" + mimeWordEncoding + "?"); + } else { + encodedStr = parts.join(""); + } + } + } else if (mimeWordEncoding === "B") { + encodedStr = libbase64.encode(data); + } + return "=?" + toCharset + "?" + mimeWordEncoding + "?" + encodedStr + (encodedStr.substr(-2) === "?=" ? "" : "?="); + } + /** + * Decodes the inner payload of a single mime encoded-word into a unicode string. + * Expects the three components of `=?charset?encoding?text?=` already separated. + * + * @param {String} charset Charset name from the encoded-word (may include an RFC 2231 language tag, which is ignored) + * @param {String} encoding Encoding indicator, either 'Q' or 'B' (case insensitive) + * @param {String} str Encoded payload between the second `?` and the trailing `?=` + * @return {String} Decoded unicode string + */ + decodeWord(charset, encoding, str) { + let splitPos = charset.indexOf("*"); + if (splitPos >= 0) { + charset = charset.substr(0, splitPos); + } + charset = libcharset.normalizeCharset(charset); + encoding = encoding.toUpperCase(); + if (encoding === "Q") { + str = str.replace(/=\s+([0-9a-fA-F])/g, "=$1").replace(/[_\s]/g, " "); + let buf = Buffer2.from(str); + let bytes = []; + for (let i = 0, len = buf.length; i < len; i++) { + let c = buf[i]; + if (i <= len - 2 && c === 61) { + let c1 = this.getHex(buf[i + 1]); + let c2 = this.getHex(buf[i + 2]); + if (c1 && c2) { + let c3 = parseInt(c1 + c2, 16); + bytes.push(c3); + i += 2; + continue; + } + } + bytes.push(c); + } + str = Buffer2.from(bytes); + } else if (encoding === "B") { + str = Buffer2.concat( + str.split("=").filter((s) => s !== "").map((str2) => Buffer2.from(str2, "base64")) + ); + } else { + str = Buffer2.from(str); + } + return libcharset.decode(str, charset); + } + /** + * Finds word sequences with non ascii text and converts these to mime words + * + * @param {String|Buffer} data String to be encoded + * @param {String} mimeWordEncoding='Q' Encoding for the mime word, either Q or B + * @param {Number} [maxLength=0] If set, split mime words into several chunks if needed + * @param {String} [fromCharset='UTF-8'] Source character set + * @return {String} String with possible mime words + */ + encodeWords(data, mimeWordEncoding, maxLength, fromCharset) { + if (!fromCharset && typeof maxLength === "string" && !maxLength.match(/^[0-9]+$/)) { + fromCharset = maxLength; + maxLength = void 0; + } + maxLength = maxLength || 0; + let decodedValue = libcharset.decode(libcharset.convert(data || "", fromCharset)); + let encodedValue; + let firstMatch = decodedValue.match(/(?:^|\s)([^\s]*[\u0080-\uFFFF])/); + if (!firstMatch) { + return decodedValue; + } + let lastMatch = decodedValue.match(/([\u0080-\uFFFF][^\s]*)[^\u0080-\uFFFF]*$/); + if (!lastMatch) { + return decodedValue; + } + let startIndex = firstMatch.index + (firstMatch[0].match(/[^\s]/) || { + index: 0 + }).index; + let endIndex = lastMatch.index + (lastMatch[1] || "").length; + encodedValue = (startIndex ? decodedValue.substr(0, startIndex) : "") + this.encodeWord(decodedValue.substring(startIndex, endIndex), mimeWordEncoding || "Q", maxLength) + (endIndex < decodedValue.length ? decodedValue.substr(endIndex) : ""); + return encodedValue; + } + /** + * Decode a string that might include one or several mime words + * + * @param {String} str String including some mime words that will be decoded + * @return {String} Decoded unicode string + */ + decodeWords(str) { + return (str || "").toString().replace(/(=\?([^?]+)\?[Bb]\?[^?]*\?=)\s*(?==\?([^?]+)\?[Bb]\?[^?]*\?=)/g, (match, left, chLeft, chRight) => { + if (libcharset.normalizeCharset(chLeft || "") === libcharset.normalizeCharset(chRight || "")) { + return left + "__\0JOIN\0__"; + } + return match; + }).replace(/(=\?([^?]+)\?[Qq]\?[^?]*\?=)\s*(?==\?([^?]+)\?[Qq]\?[^?]*\?=)/g, (match, left, chLeft, chRight) => { + if (libcharset.normalizeCharset(chLeft || "") === libcharset.normalizeCharset(chRight || "")) { + return left + "__\0JOIN\0__"; + } + return match; + }).replace(/(\?=)?__\x00JOIN\x00__(=\?([^?]+)\?[QqBb]\?)?/g, "").replace(/(=\?[^?]+\?[QqBb]\?[^?]*\?=)\s+(?==\?[^?]+\?[QqBb]\?[^?]*\?=)/g, "$1").replace(/=\?([\w_\-*]+)\?([QqBb])\?([^?]*)\?=/g, (m, charset, encoding, text) => this.decodeWord(charset, encoding, text)); + } + getHex(c) { + if (c >= 48 && c <= 57 || c >= 97 && c <= 102 || c >= 65 && c <= 70) { + return String.fromCharCode(c); + } + return false; + } + /** + * Splits a string by : + * The result is not mime word decoded, you need to do your own decoding based + * on the rules for the specific header key + * + * @param {String} headerLine Single header line, might include linebreaks as well if folded + * @return {Object} An object of {key, value} + */ + decodeHeader(headerLine) { + let line = (headerLine || "").toString().replace(/(?:\r?\n|\r)[ \t]*/g, " ").trim(), match = line.match(/^\s*([^:]+):(.*)$/), key = (match && match[1] || "").trim().toLowerCase(), value = (match && match[2] || "").trim(); + return { + key, + value + }; + } + /** + * Parses a block of header lines. Does not decode mime words as every + * header might have its own rules (eg. formatted email addresses and such) + * + * @param {String} headers Headers string + * @return {Object} An object of headers, where header keys are object keys. NB! Several values with the same key make up an Array + */ + decodeHeaders(headers) { + let lines = headers.split(/\r?\n|\r/), headersObj = {}, header, i, len; + for (i = lines.length - 1; i >= 0; i--) { + if (i && lines[i].match(/^\s/)) { + lines[i - 1] += "\r\n" + lines[i]; + lines.splice(i, 1); + } + } + for (i = 0, len = lines.length; i < len; i++) { + header = this.decodeHeader(lines[i]); + if (!headersObj[header.key]) { + headersObj[header.key] = [header.value]; + } else { + headersObj[header.key].push(header.value); + } + } + return headersObj; + } + /** + * Joins parsed header value together as 'value; param1=value1; param2=value2' + * PS: We are following RFC 822 for the list of special characters that we need to keep in quotes. + * Refer: https://www.w3.org/Protocols/rfc1341/4_Content-Type.html + * @param {Object} structured Parsed header value + * @return {String} joined header value + */ + buildHeaderValue(structured) { + let paramsArray = []; + Object.keys(structured.params || {}).forEach((param) => { + let value = structured.params[param]; + if (!this.isPlainText(value) || value.length >= 75) { + this.buildHeaderParam(param, value, 50).forEach((encodedParam) => { + if (!/[\s"\\;:/=(),<>@[\]?]|^[-']|'$/.test(encodedParam.value) || encodedParam.key.substr(-1) === "*") { + paramsArray.push(encodedParam.key + "=" + encodedParam.value); + } else { + paramsArray.push(encodedParam.key + "=" + JSON.stringify(encodedParam.value)); + } + }); + } else if (/[\s'"\\;:/=(),<>@[\]?]|^-/.test(value)) { + paramsArray.push(param + "=" + JSON.stringify(value)); + } else { + paramsArray.push(param + "=" + value); + } + }); + return structured.value + (paramsArray.length ? "; " + paramsArray.join("; ") : ""); + } + /** + * Parses a header value with key=value arguments into a structured + * object. + * + * parseHeaderValue('content-type: text/plain; CHARSET='UTF-8'') -> + * { + * 'value': 'text/plain', + * 'params': { + * 'charset': 'UTF-8' + * } + * } + * + * @param {String} str Header value + * @return {Object} Header value as a parsed structure + */ + parseHeaderValue(str) { + let response = { + value: false, + params: {} + }; + let key = false; + let value = ""; + let stage = STAGE_VALUE; + let quote = false; + let escaped = false; + let chr; + for (let i = 0, len = str.length; i < len; i++) { + chr = str.charAt(i); + switch (stage) { + case STAGE_KEY: + if (chr === "=") { + key = value.trim().toLowerCase(); + stage = STAGE_VALUE; + value = ""; + break; + } + value += chr; + break; + case STAGE_VALUE: + if (escaped) { + value += chr; + } else if (chr === "\\") { + escaped = true; + continue; + } else if (quote && chr === quote) { + quote = false; + } else if (!quote && chr === '"') { + quote = chr; + } else if (!quote && chr === ";") { + if (key === false) { + response.value = value.trim(); + } else { + response.params[key] = value.trim(); + } + stage = STAGE_KEY; + value = ""; + } else { + value += chr; + } + escaped = false; + break; + } + } + value = value.trim(); + if (stage === STAGE_VALUE) { + if (key === false) { + response.value = value; + } else { + response.params[key] = value; + } + } else if (value) { + response.params[value.toLowerCase()] = ""; + } + Object.keys(response.params).forEach((key2) => { + let actualKey; + let nr; + let value2; + let match = key2.match(/\*((\d+)\*?)?$/); + if (!match) { + return; + } + actualKey = key2.substr(0, match.index).toLowerCase(); + nr = Number(match[2]) || 0; + if (!response.params[actualKey] || typeof response.params[actualKey] !== "object") { + response.params[actualKey] = { + charset: false, + values: [] + }; + } + value2 = response.params[key2]; + if (nr === 0 && match[0].charAt(match[0].length - 1) === "*" && (match = value2.match(/^([^']*)'[^']*'(.*)$/))) { + response.params[actualKey].charset = match[1] || "utf-8"; + value2 = match[2]; + } + response.params[actualKey].values.push({ nr, value: value2 }); + delete response.params[key2]; + }); + Object.keys(response.params).forEach((key2) => { + let value2; + if (response.params[key2] && Array.isArray(response.params[key2].values)) { + value2 = response.params[key2].values.sort((a, b) => a.nr - b.nr).map((val) => val && val.value || "").join(""); + if (response.params[key2].charset) { + response.params[key2] = this.decodeWords( + "=?" + response.params[key2].charset + "?Q?" + value2.replace(/[=?_\s]/g, (s) => { + let c = s.charCodeAt(0).toString(16); + if (s === " ") { + return "_"; + } else { + return "%" + (c.length < 2 ? "0" : "") + c; + } + }).replace(/%/g, "=") + "?=" + ); + } else { + response.params[key2] = this.decodeWords(value2); + } + } + }); + return response; + } + /** + * Encodes a string or an Buffer to an UTF-8 Parameter Value Continuation encoding (rfc2231) + * Useful for splitting long parameter values. + * + * For example + * title="unicode string" + * becomes + * title*0*=utf-8''unicode + * title*1*=%20string + * + * @param {String} key Parameter name (eg. 'filename') + * @param {String|Buffer} data String to be encoded + * @param {Number} [maxLength=50] Max length for generated chunks + * @param {String} [fromCharset='UTF-8'] Source character set + * @return {Array} A list of encoded keys and headers + */ + buildHeaderParam(key, data, maxLength, fromCharset) { + let list = []; + let encodedStr = typeof data === "string" ? data : this.decode(data, fromCharset); + let encodedStrArr; + let chr, ord; + let line; + let startPos = 0; + let isEncoded = false; + let i, len; + maxLength = maxLength || 50; + if (this.isPlainText(data)) { + if (encodedStr.length <= maxLength) { + return [ + { + key, + value: encodedStr + } + ]; + } + encodedStr = encodedStr.replace(new RegExp(".{" + maxLength + "}", "g"), (str) => { + list.push({ + line: str + }); + return ""; + }); + if (encodedStr) { + list.push({ + line: encodedStr + }); + } + } else { + if (/[\uD800-\uDBFF]/.test(encodedStr)) { + encodedStrArr = []; + for (i = 0, len = encodedStr.length; i < len; i++) { + chr = encodedStr.charAt(i); + ord = chr.charCodeAt(0); + if (ord >= 55296 && ord <= 56319 && i < len - 1) { + chr += encodedStr.charAt(i + 1); + encodedStrArr.push(chr); + i++; + } else { + encodedStrArr.push(chr); + } + } + encodedStr = encodedStrArr; + } + line = "utf-8''"; + isEncoded = true; + startPos = 0; + for (i = 0, len = encodedStr.length; i < len; i++) { + chr = encodedStr[i]; + if (isEncoded) { + chr = this.safeEncodeURIComponent(chr); + } else { + chr = chr === " " ? chr : this.safeEncodeURIComponent(chr); + if (chr !== encodedStr[i]) { + if ((this.safeEncodeURIComponent(line) + chr).length >= maxLength) { + list.push({ + line, + encoded: isEncoded + }); + line = ""; + startPos = i - 1; + } else { + isEncoded = true; + i = startPos; + line = ""; + continue; + } + } + } + if ((line + chr).length >= maxLength) { + list.push({ + line, + encoded: isEncoded + }); + line = chr = encodedStr[i] === " " ? " " : this.safeEncodeURIComponent(encodedStr[i]); + if (chr === encodedStr[i]) { + isEncoded = false; + startPos = i - 1; + } else { + isEncoded = true; + } + } else { + line += chr; + } + } + if (line) { + list.push({ + line, + encoded: isEncoded + }); + } + } + return list.map((item, i2) => ({ + // encoded lines: {name}*{part}* + // unencoded lines: {name}*{part} + // if any line needs to be encoded then the first line (part==0) is always encoded + key: key + "*" + i2 + (item.encoded ? "*" : ""), + value: item.line + })); + } + /** + * Returns file extension for a content type string. If no suitable extensions + * are found, 'bin' is used as the default extension + * + * @param {String} mimeType Content type to be checked for + * @return {String} File extension + */ + detectExtension(mimeType) { + mimeType = (mimeType || "").toString().toLowerCase().replace(/\s/g, ""); + if (!(mimeType in mimetypes.list)) { + return "bin"; + } + if (typeof mimetypes.list[mimeType] === "string") { + return mimetypes.list[mimeType]; + } + let mimeParts = mimeType.split("/"); + for (let i = 0, len = mimetypes.list[mimeType].length; i < len; i++) { + if (mimeParts[1] === mimetypes.list[mimeType][i]) { + return mimetypes.list[mimeType][i]; + } + } + return mimetypes.list[mimeType][0] !== "*" ? mimetypes.list[mimeType][0] : "bin"; + } + /** + * Returns content type for a file extension. If no suitable content types + * are found, 'application/octet-stream' is used as the default content type + * + * @param {String} extension Extension (or filename) to be checked for + * @return {String} Content type + */ + detectMimeType(extension) { + extension = (extension || "").toString().toLowerCase().replace(/\s/g, "").replace(/^\./g, "").split(".").pop(); + if (!(extension in mimetypes.extensions)) { + return "application/octet-stream"; + } + if (typeof mimetypes.extensions[extension] === "string") { + return mimetypes.extensions[extension]; + } + let mimeParts; + for (let i = 0, len = mimetypes.extensions[extension].length; i < len; i++) { + mimeParts = mimetypes.extensions[extension][i].split("/"); + if (mimeParts[1] === extension) { + return mimetypes.extensions[extension][i]; + } + } + return mimetypes.extensions[extension][0]; + } + /** + * Folds long lines, useful for folding header lines (afterSpace=false) and + * flowed text (afterSpace=true) + * + * @param {String} str String to be folded + * @param {Number} [lineLength=76] Maximum length of a line + * @param {Boolean} afterSpace If true, leave a space in the end of a line + * @return {String} String with folded lines + */ + foldLines(str, lineLength, afterSpace) { + str = (str || "").toString(); + lineLength = lineLength || 76; + let pos = 0, len = str.length, result = "", line, match; + while (pos < len) { + line = str.substr(pos, lineLength); + if (line.length < lineLength) { + result += line; + break; + } + if (match = line.match(/^[^\n\r]*(\r?\n|\r)/)) { + line = match[0]; + result += line; + pos += line.length; + continue; + } else if ((match = line.match(/(\s+)[^\s]*$/)) && match[0].length - (afterSpace ? (match[1] || "").length : 0) < line.length) { + line = line.substr(0, line.length - (match[0].length - (afterSpace ? (match[1] || "").length : 0))); + } else if (match = str.substr(pos + line.length).match(/^[^\s]+(\s*)/)) { + line = line + match[0].substr(0, match[0].length - (!afterSpace ? (match[1] || "").length : 0)); + } + result += line; + pos += line.length; + if (pos < len) { + result += "\r\n"; + } + } + return result; + } + /** + * Splits a mime encoded string. Needed for dividing mime words into smaller chunks + * + * @param {String} str Mime encoded string to be split up + * @param {Number} maxlen Maximum length of characters for one part (minimum 12) + * @return {Array} Split string + */ + splitMimeEncodedString(str, maxlen) { + let curLine, match, chr, done, lines = []; + maxlen = Math.max(maxlen || 0, 12); + while (str.length) { + curLine = str.substr(0, maxlen); + if (match = curLine.match(/[=][0-9A-F]?$/i)) { + curLine = curLine.substr(0, match.index); + } + done = false; + while (!done) { + done = true; + if (match = str.substr(curLine.length).match(/^[=]([0-9A-F]{2})/i)) { + chr = parseInt(match[1], 16); + if (chr < 194 && chr > 127) { + curLine = curLine.substr(0, curLine.length - 3); + done = false; + } + } + } + if (curLine.length) { + lines.push(curLine); + } + str = str.substr(curLine.length); + } + return lines; + } + encodeURICharComponent(chr) { + let res = ""; + let ord = chr.charCodeAt(0).toString(16).toUpperCase(); + if (ord.length % 2) { + ord = "0" + ord; + } + if (ord.length > 2) { + for (let i = 0, len = ord.length / 2; i < len; i++) { + res += "%" + ord.substr(i, 2); + } + } else { + res += "%" + ord; + } + return res; + } + safeEncodeURIComponent(str) { + str = (str || "").toString(); + try { + str = encodeURIComponent(str); + } catch (E) { + return str.replace(/[^\x00-\x1F *'()<>@,;:\\"[\]?=\u007F-\uFFFF]+/g, ""); + } + return str.replace(/[\x00-\x1F *'()<>@,;:\\"[\]?=\u007F-\uFFFF]/g, (chr) => this.encodeURICharComponent(chr)); + } + }; + module.exports = new Libmime(); + module.exports.Libmime = Libmime; + } +}); + +// ../node_modules/nodemailer/lib/addressparser/index.js +var require_addressparser = __commonJS({ + "../node_modules/nodemailer/lib/addressparser/index.js"(exports, module) { + "use strict"; + function _handleAddress(tokens, depth) { + let isGroup = false; + let state = "text"; + const addresses = []; + const data = { + address: [], + comment: [], + group: [], + text: [], + textWasQuoted: [] + }; + let insideQuotes = false; + for (let i = 0, len = tokens.length; i < len; i++) { + const token = tokens[i]; + const prevToken = i ? tokens[i - 1] : null; + if (token.type === "operator") { + switch (token.value) { + case "<": + state = "address"; + insideQuotes = false; + break; + case "(": + state = "comment"; + insideQuotes = false; + break; + case ":": + state = "group"; + isGroup = true; + insideQuotes = false; + break; + case '"': + insideQuotes = !insideQuotes; + state = "text"; + break; + default: + state = "text"; + insideQuotes = false; + break; + } + } else if (token.value) { + if (state === "address") { + token.value = token.value.replace(/^[^<]*<\s*/, ""); + } + if (prevToken && prevToken.noBreak && data[state].length) { + data[state][data[state].length - 1] += token.value; + if (state === "text" && insideQuotes) { + data.textWasQuoted[data.textWasQuoted.length - 1] = true; + } + } else { + data[state].push(token.value); + if (state === "text") { + data.textWasQuoted.push(insideQuotes); + } + } + } + } + if (!data.text.length && data.comment.length) { + data.text = data.comment; + data.comment = []; + } + if (isGroup) { + data.text = data.text.join(" "); + let groupMembers = []; + if (data.group.length) { + const parsedGroup = addressparser(data.group.join(","), { _depth: depth + 1 }); + parsedGroup.forEach((member) => { + if (member.group) { + groupMembers = groupMembers.concat(member.group); + } else { + groupMembers.push(member); + } + }); + } + addresses.push({ + name: data.text || "", + group: groupMembers + }); + } else { + if (!data.address.length && data.text.length) { + for (let i = data.text.length - 1; i >= 0; i--) { + if (!data.textWasQuoted[i] && /^[^@\s]+@[^@\s]+$/.test(data.text[i])) { + data.address = data.text.splice(i, 1); + data.textWasQuoted.splice(i, 1); + break; + } + } + if (!data.address.length) { + let extracted = false; + for (let i = data.text.length - 1; i >= 0; i--) { + if (!data.textWasQuoted[i]) { + data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, (match) => { + if (!extracted) { + data.address = [match.trim()]; + extracted = true; + return " "; + } + return match; + }).trim(); + if (extracted) { + break; + } + } + } + } + } + if (!data.text.length && data.comment.length) { + data.text = data.comment; + data.comment = []; + } + if (data.address.length > 1) { + data.text = data.text.concat(data.address.splice(1)); + } + data.text = data.text.join(" "); + data.address = data.address.join(" "); + const address = { + address: data.address || data.text || "", + name: data.text || data.address || "" + }; + if (address.address === address.name) { + if (/@/.test(address.address || "")) { + address.name = ""; + } else { + address.address = ""; + } + } + addresses.push(address); + } + return addresses; + } + var Tokenizer = class { + constructor(str) { + this.str = (str || "").toString(); + this.operatorCurrent = ""; + this.operatorExpecting = ""; + this.node = null; + this.escaped = false; + this.list = []; + this.operators = { + '"': '"', + "(": ")", + "<": ">", + ",": "", + ":": ";", + // Semicolons are not a legal delimiter per the RFC2822 grammar other + // than for terminating a group, but they are also not valid for any + // other use in this context. Given that some mail clients have + // historically allowed the semicolon as a delimiter equivalent to the + // comma in their UI, it makes sense to treat them the same as a comma + // when used outside of a group. + ";": "" + }; + } + /** + * Tokenizes the original input string + * + * @return {Array} An array of operator|text tokens + */ + tokenize() { + const list = []; + for (let i = 0, len = this.str.length; i < len; i++) { + const chr = this.str.charAt(i); + const nextChr = i < len - 1 ? this.str.charAt(i + 1) : null; + this.checkChar(chr, nextChr); + } + this.list.forEach((node) => { + node.value = (node.value || "").toString().trim(); + if (node.value) { + list.push(node); + } + }); + return list; + } + /** + * Checks if a character is an operator or text and acts accordingly + * + * @param {String} chr Character from the address field + */ + checkChar(chr, nextChr) { + if (this.escaped) { + } else if (chr === this.operatorExpecting) { + this.node = { + type: "operator", + value: chr + }; + if (nextChr && ![" ", " ", "\r", "\n", ",", ";"].includes(nextChr)) { + this.node.noBreak = true; + } + this.list.push(this.node); + this.node = null; + this.operatorExpecting = ""; + this.escaped = false; + return; + } else if (!this.operatorExpecting && chr in this.operators) { + this.node = { + type: "operator", + value: chr + }; + this.list.push(this.node); + this.node = null; + this.operatorExpecting = this.operators[chr]; + this.escaped = false; + return; + } else if (['"', "'"].includes(this.operatorExpecting) && chr === "\\") { + this.escaped = true; + return; + } + if (!this.node) { + this.node = { + type: "text", + value: "" + }; + this.list.push(this.node); + } + if (chr === "\n") { + chr = " "; + } + if (chr.charCodeAt(0) >= 33 || [" ", " "].includes(chr)) { + this.node.value += chr; + } + this.escaped = false; + } + }; + var MAX_NESTED_GROUP_DEPTH = 50; + function addressparser(str, options) { + options = options || {}; + const depth = options._depth || 0; + if (depth > MAX_NESTED_GROUP_DEPTH) { + return []; + } + const tokenizer = new Tokenizer(str); + const tokens = tokenizer.tokenize(); + const addresses = []; + let address = []; + let parsedAddresses = []; + tokens.forEach((token) => { + if (token.type === "operator" && (token.value === "," || token.value === ";")) { + if (address.length) { + addresses.push(address); + } + address = []; + } else { + address.push(token); + } + }); + if (address.length) { + addresses.push(address); + } + addresses.forEach((addr) => { + const handled = _handleAddress(addr, depth); + if (handled.length) { + parsedAddresses = parsedAddresses.concat(handled); + } + }); + for (let i = parsedAddresses.length - 2; i >= 0; i--) { + const current = parsedAddresses[i]; + const next = parsedAddresses[i + 1]; + if (current.address === "" && current.name && !current.group && next.address && next.name) { + next.name = current.name + ", " + next.name; + parsedAddresses.splice(i, 1); } - }; - IconvLiteDecoderStream.prototype.collect = function(cb) { - var res = ""; - this.on("error", cb); - this.on("data", function(chunk) { - res += chunk; - }); - this.on("end", function() { - cb(null, res); - }); - return this; - }; - return { - IconvLiteEncoderStream, - IconvLiteDecoderStream - }; - }; + } + if (options.flatten) { + const flatAddresses = []; + const walkAddressList = (list) => { + list.forEach((entry) => { + if (entry.group) { + return walkAddressList(entry.group); + } + flatAddresses.push(entry); + }); + }; + walkAddressList(parsedAddresses); + return flatAddresses; + } + return parsedAddresses; + } + module.exports = addressparser; } }); -// ../node_modules/iconv-lite/lib/index.js -var require_lib2 = __commonJS({ - "../node_modules/iconv-lite/lib/index.js"(exports, module) { +// ../node_modules/punycode.js/punycode.js +var require_punycode = __commonJS({ + "../node_modules/punycode.js/punycode.js"(exports, module) { "use strict"; - var Buffer2 = require_safer().Buffer; - var bomHandling = require_bom_handling2(); - var mergeModules = require_merge_exports(); - module.exports.encodings = null; - module.exports.defaultCharUnicode = "\uFFFD"; - module.exports.defaultCharSingleByte = "?"; - module.exports.encode = function encode3(str, encoding, options) { - str = "" + (str || ""); - var encoder = module.exports.getEncoder(encoding, options); - var res = encoder.write(str); - var trail = encoder.end(); - return trail && trail.length > 0 ? Buffer2.concat([res, trail]) : res; + var maxInt = 2147483647; + var base = 36; + var tMin = 1; + var tMax = 26; + var skew = 38; + var damp = 700; + var initialBias = 72; + var initialN = 128; + var delimiter = "-"; + var regexPunycode = /^xn--/; + var regexNonASCII = /[^\0-\x7F]/; + var regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g; + var errors = { + "overflow": "Overflow: input needs wider integers to process", + "not-basic": "Illegal input >= 0x80 (not a basic code point)", + "invalid-input": "Invalid input" }; - module.exports.decode = function decode3(buf, encoding, options) { - if (typeof buf === "string") { - if (!module.exports.skipDecodeWarning) { - console.error("Iconv-lite warning: decode()-ing strings is deprecated. Refer to https://github.com/ashtuchkin/iconv-lite/wiki/Use-Buffers-when-decoding"); - module.exports.skipDecodeWarning = true; + var baseMinusTMin = base - tMin; + var floor = Math.floor; + var stringFromCharCode = String.fromCharCode; + function error2(type) { + throw new RangeError(errors[type]); + } + function map2(array2, callback) { + const result = []; + let length = array2.length; + while (length--) { + result[length] = callback(array2[length]); + } + return result; + } + function mapDomain(domain2, callback) { + const parts = domain2.split("@"); + let result = ""; + if (parts.length > 1) { + result = parts[0] + "@"; + domain2 = parts[1]; + } + domain2 = domain2.replace(regexSeparators, "."); + const labels = domain2.split("."); + const encoded = map2(labels, callback).join("."); + return result + encoded; + } + function ucs2decode(string3) { + const output = []; + let counter = 0; + const length = string3.length; + while (counter < length) { + const value = string3.charCodeAt(counter++); + if (value >= 55296 && value <= 56319 && counter < length) { + const extra = string3.charCodeAt(counter++); + if ((extra & 64512) == 56320) { + output.push(((value & 1023) << 10) + (extra & 1023) + 65536); + } else { + output.push(value); + counter--; + } + } else { + output.push(value); } - buf = Buffer2.from("" + (buf || ""), "binary"); } - var decoder = module.exports.getDecoder(encoding, options); - var res = decoder.write(buf); - var trail = decoder.end(); - return trail ? res + trail : res; + return output; + } + var ucs2encode = (codePoints) => String.fromCodePoint(...codePoints); + var basicToDigit = function(codePoint) { + if (codePoint >= 48 && codePoint < 58) { + return 26 + (codePoint - 48); + } + if (codePoint >= 65 && codePoint < 91) { + return codePoint - 65; + } + if (codePoint >= 97 && codePoint < 123) { + return codePoint - 97; + } + return base; }; - module.exports.encodingExists = function encodingExists(enc) { - try { - module.exports.getCodec(enc); - return true; - } catch (e) { - return false; + var digitToBasic = function(digit, flag) { + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + }; + var adapt = function(delta, numPoints, firstTime) { + let k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); }; - module.exports.toEncoding = module.exports.encode; - module.exports.fromEncoding = module.exports.decode; - module.exports._codecDataCache = { __proto__: null }; - module.exports.getCodec = function getCodec(encoding) { - if (!module.exports.encodings) { - var raw = require_encodings2(); - module.exports.encodings = { __proto__: null }; - mergeModules(module.exports.encodings, raw); + var decode3 = function(input) { + const output = []; + const inputLength = input.length; + let i = 0; + let n = initialN; + let bias = initialBias; + let basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; } - var enc = module.exports._canonicalizeEncoding(encoding); - var codecOptions = {}; - while (true) { - var codec2 = module.exports._codecDataCache[enc]; - if (codec2) { - return codec2; + for (let j = 0; j < basic; ++j) { + if (input.charCodeAt(j) >= 128) { + error2("not-basic"); } - var codecDef = module.exports.encodings[enc]; - switch (typeof codecDef) { - case "string": - enc = codecDef; - break; - case "object": - for (var key in codecDef) { - codecOptions[key] = codecDef[key]; - } - if (!codecOptions.encodingName) { - codecOptions.encodingName = enc; - } - enc = codecDef.type; + output.push(input.charCodeAt(j)); + } + for (let index = basic > 0 ? basic + 1 : 0; index < inputLength; ) { + const oldi = i; + for (let w = 1, k = base; ; k += base) { + if (index >= inputLength) { + error2("invalid-input"); + } + const digit = basicToDigit(input.charCodeAt(index++)); + if (digit >= base) { + error2("invalid-input"); + } + if (digit > floor((maxInt - i) / w)) { + error2("overflow"); + } + i += digit * w; + const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias; + if (digit < t) { break; - case "function": - if (!codecOptions.encodingName) { - codecOptions.encodingName = enc; + } + const baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error2("overflow"); + } + w *= baseMinusT; + } + const out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + if (floor(i / out) > maxInt - n) { + error2("overflow"); + } + n += floor(i / out); + i %= out; + output.splice(i++, 0, n); + } + return String.fromCodePoint(...output); + }; + var encode3 = function(input) { + const output = []; + input = ucs2decode(input); + const inputLength = input.length; + let n = initialN; + let delta = 0; + let bias = initialBias; + for (const currentValue of input) { + if (currentValue < 128) { + output.push(stringFromCharCode(currentValue)); + } + } + const basicLength = output.length; + let handledCPCount = basicLength; + if (basicLength) { + output.push(delimiter); + } + while (handledCPCount < inputLength) { + let m = maxInt; + for (const currentValue of input) { + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + const handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error2("overflow"); + } + delta += (m - n) * handledCPCountPlusOne; + n = m; + for (const currentValue of input) { + if (currentValue < n && ++delta > maxInt) { + error2("overflow"); + } + if (currentValue === n) { + let q = delta; + for (let k = base; ; k += base) { + const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias; + if (q < t) { + break; + } + const qMinusT = q - t; + const baseMinusT = base - t; + output.push( + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) + ); + q = floor(qMinusT / baseMinusT); } - codec2 = new codecDef(codecOptions, module.exports); - module.exports._codecDataCache[codecOptions.encodingName] = codec2; - return codec2; - default: - throw new Error("Encoding not recognized: '" + encoding + "' (searched as: '" + enc + "')"); + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount === basicLength); + delta = 0; + ++handledCPCount; + } } + ++delta; + ++n; } + return output.join(""); }; - module.exports._canonicalizeEncoding = function(encoding) { - return ("" + encoding).toLowerCase().replace(/:\d{4}$|[^0-9a-z]/g, ""); + var toUnicode = function(input) { + return mapDomain(input, function(string3) { + return regexPunycode.test(string3) ? decode3(string3.slice(4).toLowerCase()) : string3; + }); }; - module.exports.getEncoder = function getEncoder(encoding, options) { - var codec2 = module.exports.getCodec(encoding); - var encoder = new codec2.encoder(options, codec2); - if (codec2.bomAware && options && options.addBOM) { - encoder = new bomHandling.PrependBOM(encoder, options); - } - return encoder; + var toASCII = function(input) { + return mapDomain(input, function(string3) { + return regexNonASCII.test(string3) ? "xn--" + encode3(string3) : string3; + }); }; - module.exports.getDecoder = function getDecoder(encoding, options) { - var codec2 = module.exports.getCodec(encoding); - var decoder = new codec2.decoder(options, codec2); - if (codec2.bomAware && !(options && options.stripBOM === false)) { - decoder = new bomHandling.StripBOM(decoder, options); - } - return decoder; + var punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + "version": "2.3.1", + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + "ucs2": { + "decode": ucs2decode, + "encode": ucs2encode + }, + "decode": decode3, + "encode": encode3, + "toASCII": toASCII, + "toUnicode": toUnicode }; - module.exports.enableStreamingAPI = function enableStreamingAPI(streamModule2) { - if (module.exports.supportsStreams) { - return; + module.exports = punycode; + } +}); + +// ../node_modules/mailparser/lib/stream-hash.js +var require_stream_hash = __commonJS({ + "../node_modules/mailparser/lib/stream-hash.js"(exports, module) { + "use strict"; + var crypto = __require("crypto"); + var Transform = __require("stream").Transform; + var StreamHash = class extends Transform { + constructor(attachment, algo) { + super(); + this.attachment = attachment; + this.algo = (algo || "md5").toLowerCase(); + this.hash = crypto.createHash(algo); + this.byteCount = 0; + } + _transform(chunk, encoding, done) { + this.hash.update(chunk); + this.byteCount += chunk.length; + done(null, chunk); + } + _flush(done) { + this.attachment.checksum = this.hash.digest("hex"); + this.attachment.size = this.byteCount; + done(); } - var streams = require_streams2()(streamModule2); - module.exports.IconvLiteEncoderStream = streams.IconvLiteEncoderStream; - module.exports.IconvLiteDecoderStream = streams.IconvLiteDecoderStream; - module.exports.encodeStream = function encodeStream(encoding, options) { - return new module.exports.IconvLiteEncoderStream(module.exports.getEncoder(encoding, options), options); - }; - module.exports.decodeStream = function decodeStream(encoding, options) { - return new module.exports.IconvLiteDecoderStream(module.exports.getDecoder(encoding, options), options); - }; - module.exports.supportsStreams = true; }; - var streamModule; - try { - streamModule = __require("stream"); - } catch (e) { - } - if (streamModule && streamModule.Transform) { - module.exports.enableStreamingAPI(streamModule); - } else { - module.exports.encodeStream = module.exports.decodeStream = function() { - throw new Error("iconv-lite Streaming API is not enabled. Use iconv.enableStreamingAPI(require('stream')); to enable it."); - }; - } - if (false) { - console.error("iconv-lite warning: js files use non-utf8 encoding. See https://github.com/ashtuchkin/iconv-lite/wiki/Javascript-source-file-encodings for more info."); - } + module.exports = StreamHash; } }); @@ -44636,7 +47716,7 @@ var require_mail_parser = __commonJS({ "../node_modules/mailparser/lib/mail-parser.js"(exports, module) { "use strict"; var mailsplit = require_mailsplit(); - var libmime = require_libmime(); + var libmime = require_libmime2(); var addressparser = require_addressparser(); var Transform = __require("stream").Transform; var Splitter = mailsplit.Splitter; @@ -44921,7 +48001,7 @@ var require_mail_parser = __commonJS({ value = this.libmime.decodeWords(value); } catch (E) { } - value = value.split(/\s+/).map(this.ensureMessageIDFormat); + value = value.split(/\s+/).map(this.ensureMessageIDFormat).filter((val) => val); break; case "message-id": case "in-reply-to": @@ -45078,16 +48158,22 @@ var require_mail_parser = __commonJS({ let address = addresses[i]; address.name = (address.name || "").toString().trim(); if (!address.address && /^(=\?([^?]+)\?[Bb]\?[^?]*\?=)(\s*=\?([^?]+)\?[Bb]\?[^?]*\?=)*$/.test(address.name) && !processedAddress.has(address)) { - let parsed = addressparser(this.libmime.decodeWords(address.name)); - if (parsed.length) { - parsed.forEach((entry) => { - processedAddress.add(entry); - addresses.push(entry); - }); + let decoded = this.libmime.decodeWords(address.name); + if (/<[^<>]+@[^<>]+>/.test(decoded)) { + let parsed = addressparser(decoded); + if (parsed.length) { + parsed.forEach((entry) => { + processedAddress.add(entry); + addresses.push(entry); + }); + } + addresses.splice(i, 1); + i--; + continue; + } else { + address.name = decoded; + continue; } - addresses.splice(i, 1); - i--; - continue; } if (address.name) { try { @@ -45095,6 +48181,18 @@ var require_mail_parser = __commonJS({ } catch (E) { } } + if (address.address && /[=]\?[^?]+\?[BbQq]\?[^?]*\?[=]/.test(address.address)) { + try { + let decodedAddr = this.libmime.decodeWords(address.address); + if (/^[^\s@]+@[^\s@]+$/.test(decodedAddr) && !/[=]\?/.test(decodedAddr)) { + address.address = decodedAddr; + } else { + address.address = ""; + } + } catch (E) { + address.address = ""; + } + } if (/@xn--/.test(address.address)) { try { address.address = address.address.substr(0, address.address.lastIndexOf("@") + 1) + punycode.toUnicode(address.address.substr(address.address.lastIndexOf("@") + 1)); @@ -62516,7 +65614,7 @@ var require_turndown_cjs = __commonJS({ for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { - if (source.hasOwnProperty(key)) + if (Object.prototype.hasOwnProperty.call(source, key)) destination[key] = source[key]; } } @@ -62537,97 +65635,18 @@ var require_turndown_cjs = __commonJS({ function trimNewlines(string3) { return trimTrailingNewlines(trimLeadingNewlines(string3)); } - var blockElements = [ - "ADDRESS", - "ARTICLE", - "ASIDE", - "AUDIO", - "BLOCKQUOTE", - "BODY", - "CANVAS", - "CENTER", - "DD", - "DIR", - "DIV", - "DL", - "DT", - "FIELDSET", - "FIGCAPTION", - "FIGURE", - "FOOTER", - "FORM", - "FRAMESET", - "H1", - "H2", - "H3", - "H4", - "H5", - "H6", - "HEADER", - "HGROUP", - "HR", - "HTML", - "ISINDEX", - "LI", - "MAIN", - "MENU", - "NAV", - "NOFRAMES", - "NOSCRIPT", - "OL", - "OUTPUT", - "P", - "PRE", - "SECTION", - "TABLE", - "TBODY", - "TD", - "TFOOT", - "TH", - "THEAD", - "TR", - "UL" - ]; + var blockElements = ["ADDRESS", "ARTICLE", "ASIDE", "AUDIO", "BLOCKQUOTE", "BODY", "CANVAS", "CENTER", "DD", "DIR", "DIV", "DL", "DT", "FIELDSET", "FIGCAPTION", "FIGURE", "FOOTER", "FORM", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEADER", "HGROUP", "HR", "HTML", "ISINDEX", "LI", "MAIN", "MENU", "NAV", "NOFRAMES", "NOSCRIPT", "OL", "OUTPUT", "P", "PRE", "SECTION", "TABLE", "TBODY", "TD", "TFOOT", "TH", "THEAD", "TR", "UL"]; function isBlock(node) { return is(node, blockElements); } - var voidElements = [ - "AREA", - "BASE", - "BR", - "COL", - "COMMAND", - "EMBED", - "HR", - "IMG", - "INPUT", - "KEYGEN", - "LINK", - "META", - "PARAM", - "SOURCE", - "TRACK", - "WBR" - ]; + var voidElements = ["AREA", "BASE", "BR", "COL", "COMMAND", "EMBED", "HR", "IMG", "INPUT", "KEYGEN", "LINK", "META", "PARAM", "SOURCE", "TRACK", "WBR"]; function isVoid(node) { return is(node, voidElements); } function hasVoid(node) { return has(node, voidElements); } - var meaningfulWhenBlankElements = [ - "A", - "TABLE", - "THEAD", - "TBODY", - "TFOOT", - "TH", - "TD", - "IFRAME", - "SCRIPT", - "AUDIO", - "VIDEO" - ]; + var meaningfulWhenBlankElements = ["A", "TABLE", "THEAD", "TBODY", "TFOOT", "TH", "TD", "IFRAME", "SCRIPT", "AUDIO", "VIDEO"]; function isMeaningfulWhenBlank(node) { return is(node, meaningfulWhenBlankElements); } @@ -62642,6 +65661,12 @@ var require_turndown_cjs = __commonJS({ return node.getElementsByTagName(tagName).length; }); } + var markdownEscapes = [[/\\/g, "\\\\"], [/\*/g, "\\*"], [/^-/g, "\\-"], [/^\+ /g, "\\+ "], [/^(=+)/g, "\\$1"], [/^(#{1,6}) /g, "\\$1 "], [/`/g, "\\`"], [/^~~~/g, "\\~~~"], [/\[/g, "\\["], [/\]/g, "\\]"], [/^>/g, "\\>"], [/_/g, "\\_"], [/^(\d+)\. /g, "$1\\. "]]; + function escapeMarkdown(string3) { + return markdownEscapes.reduce(function(accumulator, escape2) { + return accumulator.replace(escape2[0], escape2[1]); + }, string3); + } var rules = {}; rules.paragraph = { filter: "p", @@ -62741,13 +65766,10 @@ var require_turndown_cjs = __commonJS({ return options.linkStyle === "inlined" && node.nodeName === "A" && node.getAttribute("href"); }, replacement: function(content, node) { - var href = node.getAttribute("href"); - if (href) - href = href.replace(/([()])/g, "\\$1"); - var title = cleanAttribute(node.getAttribute("title")); - if (title) - title = ' "' + title.replace(/"/g, '\\"') + '"'; - return "[" + content + "](" + href + title + ")"; + var href = escapeLinkDestination(node.getAttribute("href")); + var title = escapeLinkTitle(cleanAttribute(node.getAttribute("title"))); + var titlePart = title ? ' "' + title + '"' : ""; + return "[" + content + "](" + href + titlePart + ")"; } }; rules.referenceLink = { @@ -62755,10 +65777,10 @@ var require_turndown_cjs = __commonJS({ return options.linkStyle === "referenced" && node.nodeName === "A" && node.getAttribute("href"); }, replacement: function(content, node, options) { - var href = node.getAttribute("href"); + var href = escapeLinkDestination(node.getAttribute("href")); var title = cleanAttribute(node.getAttribute("title")); if (title) - title = ' "' + title + '"'; + title = ' "' + escapeLinkTitle(title) + '"'; var replacement; var reference; switch (options.linkReferenceStyle) { @@ -62825,16 +65847,23 @@ var require_turndown_cjs = __commonJS({ rules.image = { filter: "img", replacement: function(content, node) { - var alt = cleanAttribute(node.getAttribute("alt")); - var src = node.getAttribute("src") || ""; + var alt = escapeMarkdown(cleanAttribute(node.getAttribute("alt"))); + var src = escapeLinkDestination(node.getAttribute("src") || ""); var title = cleanAttribute(node.getAttribute("title")); - var titlePart = title ? ' "' + title + '"' : ""; + var titlePart = title ? ' "' + escapeLinkTitle(title) + '"' : ""; return src ? "![" + alt + "](" + src + titlePart + ")" : ""; } }; function cleanAttribute(attribute) { return attribute ? attribute.replace(/(\n+\s*)+/g, "\n") : ""; } + function escapeLinkDestination(destination) { + var escaped = destination.replace(/([<>()])/g, "\\$1"); + return escaped.indexOf(" ") >= 0 ? "<" + escaped + ">" : escaped; + } + function escapeLinkTitle(title) { + return title.replace(/"/g, '\\"'); + } function Rules(options) { this.options = options; this._keep = []; @@ -63038,7 +66067,10 @@ var require_turndown_cjs = __commonJS({ } function flankingWhitespace(node, options) { if (node.isBlock || options.preformattedCode && node.isCode) { - return { leading: "", trailing: "" }; + return { + leading: "", + trailing: "" + }; } var edges = edgeWhitespace(node.textContent); if (edges.leadingAscii && isFlankedByWhitespace("left", node, options)) { @@ -63047,7 +66079,10 @@ var require_turndown_cjs = __commonJS({ if (edges.trailingAscii && isFlankedByWhitespace("right", node, options)) { edges.trailing = edges.trailingNonAscii; } - return { leading: edges.leading, trailing: edges.trailing }; + return { + leading: edges.leading, + trailing: edges.trailing + }; } function edgeWhitespace(string3) { var m = string3.match(/^(([ \t\r\n]*)(\s*))(?:(?=\S)[\s\S]*\S)?((\s*?)([ \t\r\n]*))$/); @@ -63085,21 +66120,6 @@ var require_turndown_cjs = __commonJS({ return isFlanked; } var reduce = Array.prototype.reduce; - var escapes = [ - [/\\/g, "\\\\"], - [/\*/g, "\\*"], - [/^-/g, "\\-"], - [/^\+ /g, "\\+ "], - [/^(=+)/g, "\\$1"], - [/^(#{1,6}) /g, "\\$1 "], - [/`/g, "\\`"], - [/^~~~/g, "\\~~~"], - [/\[/g, "\\["], - [/\]/g, "\\]"], - [/^>/g, "\\>"], - [/_/g, "\\_"], - [/^(\d+)\. /g, "$1\\. "] - ]; function TurndownService2(options) { if (!(this instanceof TurndownService2)) return new TurndownService2(options); @@ -63139,9 +66159,7 @@ var require_turndown_cjs = __commonJS({ */ turndown: function(input) { if (!canConvert(input)) { - throw new TypeError( - input + " is not a string, or an element/document/fragment node." - ); + throw new TypeError(input + " is not a string, or an element/document/fragment node."); } if (input === "") return ""; @@ -63208,9 +66226,7 @@ var require_turndown_cjs = __commonJS({ * @type String */ escape: function(string3) { - return escapes.reduce(function(accumulator, escape2) { - return accumulator.replace(escape2[0], escape2[1]); - }, string3); + return escapeMarkdown(string3); } }; function process4(parentNode) { @@ -68779,7 +71795,7 @@ var Doc = class { var version = { major: 4, minor: 3, - patch: 5 + patch: 6 }; // node_modules/zod/v4/core/schemas.js @@ -70070,7 +73086,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => { if (keyResult instanceof Promise) { throw new Error("Async schemas not supported in object keys currently"); } - const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length && keyResult.issues.some((iss) => iss.code === "invalid_type" && iss.expected === "number"); + const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length; if (checkNumericKey) { const retryResult = def.keyType._zod.run({ value: Number(key), issues: [] }, ctx); if (retryResult instanceof Promise) { @@ -71915,7 +74931,7 @@ function finalize(ctx, schema) { } } } - if (refSchema.$ref) { + if (refSchema.$ref && refSeen.def) { for (const key in schema2) { if (key === "$ref" || key === "allOf") continue; @@ -74009,10 +77025,9 @@ var ProgressTokenSchema = union([string2(), number2().int()]); var CursorSchema = string2(); var TaskCreationParamsSchema = looseObject({ /** - * Time in milliseconds to keep task results available after completion. - * If null, the task has unlimited lifetime until manually cleaned up. + * Requested duration in milliseconds to retain task from creation. */ - ttl: union([number2(), _null3()]).optional(), + ttl: number2().optional(), /** * Time in milliseconds to wait between task status requests. */ @@ -74312,7 +77327,11 @@ var ClientCapabilitiesSchema = object2({ /** * Present if the client supports task creation. */ - tasks: ClientTasksCapabilitySchema.optional() + tasks: ClientTasksCapabilitySchema.optional(), + /** + * Extensions that the client supports. Keys are extension identifiers (vendor-prefix/extension-name). + */ + extensions: record(string2(), AssertObjectSchema).optional() }); var InitializeRequestParamsSchema = BaseRequestParamsSchema.extend({ /** @@ -74373,7 +77392,11 @@ var ServerCapabilitiesSchema = object2({ /** * Present if the server supports task creation. */ - tasks: ServerTasksCapabilitySchema.optional() + tasks: ServerTasksCapabilitySchema.optional(), + /** + * Extensions that the server supports. Keys are extension identifiers (vendor-prefix/extension-name). + */ + extensions: record(string2(), AssertObjectSchema).optional() }); var InitializeResultSchema = ResultSchema.extend({ /** @@ -74565,6 +77588,12 @@ var ResourceSchema = object2({ * The MIME type of this resource, if known. */ mimeType: optional(string2()), + /** + * The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + * + * This can be used by Hosts to display file sizes and estimate context window usage. + */ + size: optional(number2()), /** * Optional annotations for the client. */ @@ -75714,6 +78743,9 @@ var Protocol = class { * The Protocol object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward. */ async connect(transport) { + if (this._transport) { + throw new Error("Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection."); + } this._transport = transport; const _onclose = this.transport?.onclose; this._transport.onclose = () => { @@ -75746,6 +78778,14 @@ var Protocol = class { this._progressHandlers.clear(); this._taskProgressTokens.clear(); this._pendingDebouncedNotifications.clear(); + for (const info of this._timeoutInfo.values()) { + clearTimeout(info.timeoutId); + } + this._timeoutInfo.clear(); + for (const controller of this._requestHandlerAbortControllers.values()) { + controller.abort(); + } + this._requestHandlerAbortControllers.clear(); const error2 = McpError.fromError(ErrorCode.ConnectionClosed, "Connection closed"); this._transport = void 0; this.onclose?.(); @@ -75796,6 +78836,8 @@ var Protocol = class { sessionId: capturedTransport?.sessionId, _meta: request.params?._meta, sendNotification: async (notification) => { + if (abortController.signal.aborted) + return; const notificationOptions = { relatedRequestId: request.id }; if (relatedTaskId) { notificationOptions.relatedTask = { taskId: relatedTaskId }; @@ -75803,6 +78845,9 @@ var Protocol = class { await this.notification(notification, notificationOptions); }, sendRequest: async (r, resultSchema, options) => { + if (abortController.signal.aborted) { + throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled"); + } const requestOptions = { ...options, relatedRequestId: request.id }; if (relatedTaskId && !requestOptions.relatedTask) { requestOptions.relatedTask = { taskId: relatedTaskId }; @@ -75867,7 +78912,9 @@ var Protocol = class { await capturedTransport?.send(errorResponse); } }).catch((error2) => this._onerror(new Error(`Failed to send response: ${error2}`))).finally(() => { - this._requestHandlerAbortControllers.delete(request.id); + if (this._requestHandlerAbortControllers.get(request.id) === abortController) { + this._requestHandlerAbortControllers.delete(request.id); + } }); } _onprogress(notification) { @@ -76563,6 +79610,147 @@ var ExperimentalServerTasks = class { requestStream(request, resultSchema, options) { return this._server.requestStream(request, resultSchema, options); } + /** + * Sends a sampling request and returns an AsyncGenerator that yields response messages. + * The generator is guaranteed to end with either a 'result' or 'error' message. + * + * For task-augmented requests, yields 'taskCreated' and 'taskStatus' messages + * before the final result. + * + * @example + * ```typescript + * const stream = server.experimental.tasks.createMessageStream({ + * messages: [{ role: 'user', content: { type: 'text', text: 'Hello' } }], + * maxTokens: 100 + * }, { + * onprogress: (progress) => { + * // Handle streaming tokens via progress notifications + * console.log('Progress:', progress.message); + * } + * }); + * + * for await (const message of stream) { + * switch (message.type) { + * case 'taskCreated': + * console.log('Task created:', message.task.taskId); + * break; + * case 'taskStatus': + * console.log('Task status:', message.task.status); + * break; + * case 'result': + * console.log('Final result:', message.result); + * break; + * case 'error': + * console.error('Error:', message.error); + * break; + * } + * } + * ``` + * + * @param params - The sampling request parameters + * @param options - Optional request options (timeout, signal, task creation params, onprogress, etc.) + * @returns AsyncGenerator that yields ResponseMessage objects + * + * @experimental + */ + createMessageStream(params, options) { + const clientCapabilities = this._server.getClientCapabilities(); + if ((params.tools || params.toolChoice) && !clientCapabilities?.sampling?.tools) { + throw new Error("Client does not support sampling tools capability."); + } + if (params.messages.length > 0) { + const lastMessage = params.messages[params.messages.length - 1]; + const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content]; + const hasToolResults = lastContent.some((c) => c.type === "tool_result"); + const previousMessage = params.messages.length > 1 ? params.messages[params.messages.length - 2] : void 0; + const previousContent = previousMessage ? Array.isArray(previousMessage.content) ? previousMessage.content : [previousMessage.content] : []; + const hasPreviousToolUse = previousContent.some((c) => c.type === "tool_use"); + if (hasToolResults) { + if (lastContent.some((c) => c.type !== "tool_result")) { + throw new Error("The last message must contain only tool_result content if any is present"); + } + if (!hasPreviousToolUse) { + throw new Error("tool_result blocks are not matching any tool_use from the previous message"); + } + } + if (hasPreviousToolUse) { + const toolUseIds = new Set(previousContent.filter((c) => c.type === "tool_use").map((c) => c.id)); + const toolResultIds = new Set(lastContent.filter((c) => c.type === "tool_result").map((c) => c.toolUseId)); + if (toolUseIds.size !== toolResultIds.size || ![...toolUseIds].every((id) => toolResultIds.has(id))) { + throw new Error("ids of tool_result blocks and tool_use blocks from previous message do not match"); + } + } + } + return this.requestStream({ + method: "sampling/createMessage", + params + }, CreateMessageResultSchema, options); + } + /** + * Sends an elicitation request and returns an AsyncGenerator that yields response messages. + * The generator is guaranteed to end with either a 'result' or 'error' message. + * + * For task-augmented requests (especially URL-based elicitation), yields 'taskCreated' + * and 'taskStatus' messages before the final result. + * + * @example + * ```typescript + * const stream = server.experimental.tasks.elicitInputStream({ + * mode: 'url', + * message: 'Please authenticate', + * elicitationId: 'auth-123', + * url: 'https://example.com/auth' + * }, { + * task: { ttl: 300000 } // Task-augmented for long-running auth flow + * }); + * + * for await (const message of stream) { + * switch (message.type) { + * case 'taskCreated': + * console.log('Task created:', message.task.taskId); + * break; + * case 'taskStatus': + * console.log('Task status:', message.task.status); + * break; + * case 'result': + * console.log('User action:', message.result.action); + * break; + * case 'error': + * console.error('Error:', message.error); + * break; + * } + * } + * ``` + * + * @param params - The elicitation request parameters + * @param options - Optional request options (timeout, signal, task creation params, etc.) + * @returns AsyncGenerator that yields ResponseMessage objects + * + * @experimental + */ + elicitInputStream(params, options) { + const clientCapabilities = this._server.getClientCapabilities(); + const mode = params.mode ?? "form"; + switch (mode) { + case "url": { + if (!clientCapabilities?.elicitation?.url) { + throw new Error("Client does not support url elicitation."); + } + break; + } + case "form": { + if (!clientCapabilities?.elicitation?.form) { + throw new Error("Client does not support form elicitation."); + } + break; + } + } + const normalizedParams = mode === "form" && params.mode === void 0 ? { ...params, mode: "form" } : params; + return this.requestStream({ + method: "elicitation/create", + params: normalizedParams + }, ElicitResultSchema, options); + } /** * Gets the current status of a task. * @@ -77578,13 +80766,13 @@ var tools = [ }, { name: "contact", - description: "Manage macOS contacts. Actions: groups (list groups), list (list contacts), search (by name/email/phone), get (by ID with photo), create, update, delete, schema (show input schema).", + description: "Manage macOS contacts. Actions: containers (list contact accounts), groups (list groups), list (list contacts), search (by name/email/phone), get (by ID with photo), create, update, delete, schema (show input schema).", inputSchema: { type: "object", properties: { action: { type: "string", - enum: ["groups", "list", "search", "get", "create", "update", "delete", "schema"], + enum: ["containers", "groups", "list", "search", "get", "create", "update", "delete", "schema"], description: "Operation to perform" }, ...agentDXProperties, @@ -77710,6 +80898,7 @@ var tools = [ } }, notes: { type: "string" }, + container: { type: "string", description: "Target container/account name for create" }, configDir: { type: "string", description: "Override PIM config directory (OpenClaw only \u2014 ignored by MCP server)" }, profile: { type: "string", description: "Override PIM profile name (OpenClaw only \u2014 MCP server uses APPLE_PIM_PROFILE env)" } }, @@ -78206,6 +81395,8 @@ function buildContactCreateArgs(args) { const cliArgs = ["create"]; if (args.name) cliArgs.push("--name", args.name); + if (args.container) + cliArgs.push("--container", args.container); pushContactSharedFields(cliArgs, args); return cliArgs; } @@ -78365,6 +81556,8 @@ async function handleContact(args, runCLI2) { switch (args.action) { case "groups": return await runCLI2("contacts-cli", ["groups"]); + case "containers": + return await runCLI2("contacts-cli", ["containers"]); case "list": cliArgs.push("list"); if (args.group) diff --git a/mcp-server/package.json b/mcp-server/package.json index 14953bd..7604593 100644 --- a/mcp-server/package.json +++ b/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "apple-pim-mcp", - "version": "3.7.20", + "version": "3.7.22", "description": "MCP server for Apple PIM (Calendar, Reminders, Contacts, Mail)", "type": "module", "main": "dist/server.js", diff --git a/mcp-server/test/tool-args.test.js b/mcp-server/test/tool-args.test.js index 0480bef..8cd9f24 100644 --- a/mcp-server/test/tool-args.test.js +++ b/mcp-server/test/tool-args.test.js @@ -169,6 +169,24 @@ describe("buildContactCreateArgs", () => { expect(args).toEqual(["create", "--name", "Ada Lovelace", "--notes", "Test"]); }); + it("passes --container when container is specified", () => { + const args = buildContactCreateArgs({ + name: "Jane Doe", + container: "iCloud", + email: "jane@example.com", + }); + expect(args).toContain("--container"); + const idx = args.indexOf("--container"); + expect(args[idx + 1]).toBe("iCloud"); + }); + + it("omits --container when not specified", () => { + const args = buildContactCreateArgs({ + name: "Jane Doe", + }); + expect(args).not.toContain("--container"); + }); + it("maps non-empty rich arrays as JSON", () => { const args = buildContactCreateArgs({ firstName: "Ada", diff --git a/openclaw/openclaw.plugin.json b/openclaw/openclaw.plugin.json index c2aa285..55096f9 100644 --- a/openclaw/openclaw.plugin.json +++ b/openclaw/openclaw.plugin.json @@ -2,7 +2,7 @@ "id": "apple-pim-cli", "name": "Apple PIM", "description": "macOS-only. Wraps four native Swift CLIs (calendar-cli, reminder-cli, contacts-cli, mail-cli) you build locally from source via ./setup.sh — no binaries are downloaded by the registry. Grants the agent read/write access to Calendar, Reminders, Contacts, and Mail.app (including send/delete) once you approve the corresponding macOS TCC and Automation prompts.", - "version": "3.7.20", + "version": "3.7.22", "platforms": [ "darwin" ], diff --git a/openclaw/package.json b/openclaw/package.json index 1de594d..8112181 100644 --- a/openclaw/package.json +++ b/openclaw/package.json @@ -1,6 +1,6 @@ { "name": "apple-pim-cli", - "version": "3.7.20", + "version": "3.7.22", "description": "OpenClaw plugin for macOS Calendar, Reminders, Contacts, and Mail. macOS-only; depends on four native Swift CLIs (calendar-cli, reminder-cli, contacts-cli, mail-cli) you build locally from source via ./setup.sh. The registry does not download or install binaries.", "type": "module", "scripts": { diff --git a/swift/Sources/ContactsCLI/ContactsCLI.swift b/swift/Sources/ContactsCLI/ContactsCLI.swift index 848a354..d9c3e92 100644 --- a/swift/Sources/ContactsCLI/ContactsCLI.swift +++ b/swift/Sources/ContactsCLI/ContactsCLI.swift @@ -10,6 +10,7 @@ struct ContactsCLI: AsyncParsableCommand { abstract: "Manage macOS Contacts", subcommands: [ AuthStatus.self, + ListContainers.self, ListGroups.self, ListContacts.self, SearchContacts.self, @@ -315,6 +316,22 @@ let keysToFetch: [CNKeyDescriptor] = [ CNContactFormatter.descriptorForRequiredKeys(for: .fullName), ] +func containerToDict(_ container: CNContainer) -> [String: Any] { + let typeName: String + switch container.type { + case .local: typeName = "local" + case .exchange: typeName = "exchange" + case .cardDAV: typeName = "cardDAV" + case .unassigned: typeName = "unassigned" + @unknown default: typeName = "unknown" + } + return [ + "id": container.identifier, + "name": container.name, + "type": typeName + ] +} + func groupToDict(_ group: CNGroup) -> [String: Any] { return [ "id": group.identifier, @@ -493,8 +510,140 @@ func contactToDict(_ contact: CNContact, brief: Bool = false) -> [String: Any] { return dict } +// MARK: - Container Filtering + +// containers(matching: nil) returns real accounts but also Exchange default lists as Contacts groups (Apple bug). +// Strip those by excluding any container whose identifier also appears in groups(matching: nil). +func allAccountContainers() throws -> [CNContainer] { + let all = try contactStore.containers(matching: nil) + let groupIds = Set(try contactStore.groups(matching: nil).map { $0.identifier }) + return all.filter { !groupIds.contains($0.identifier) } +} + +func filteredContainers(config: PIMConfiguration) throws -> [CNContainer] { + let accounts = try allAccountContainers() + return ItemFilter.filter(items: accounts, config: config.contacts, name: { $0.name }, id: { $0.identifier }) +} + +func fetchContactsFromAllowedContainers(config: PIMConfiguration) throws -> [CNContact] { + let allowed = try filteredContainers(config: config) + var contacts: [CNContact] = [] + for container in allowed { + let request = CNContactFetchRequest(keysToFetch: keysToFetch) + request.predicate = CNContact.predicateForContactsInContainer(withIdentifier: container.identifier) + request.unifyResults = false + request.mutableObjects = false + try contactStore.enumerateContacts(with: request) { contact, _ in + contacts.append(contact) + } + } + return contacts +} + +// MARK: - Scoped Contact Resolution + +enum ContactAccessMode { + case fullAccess + case scopedContainers(Set) +} + +struct AuthorizedRawContact { + let contact: CNContact + let accountContainer: CNContainer +} + +func contactAccessMode(config: PIMConfiguration) -> ContactAccessMode { + guard config.contacts.mode != .all else { return .fullAccess } + let allowed = (try? filteredContainers(config: config)) ?? [] + return .scopedContainers(Set(allowed.map { $0.identifier })) +} + +func resolveAccountContainer(forContactId contactId: String) throws -> CNContainer? { + let containerPred = CNContainer.predicateForContainerOfContact(withIdentifier: contactId) + let containers = try contactStore.containers(matching: containerPred) + guard let direct = containers.first else { return nil } + + let groupIds = Set(try contactStore.groups(matching: nil).map { $0.identifier }) + if groupIds.contains(direct.identifier) { + let parentPred = CNContainer.predicateForContainerOfGroup(withIdentifier: direct.identifier) + return try contactStore.containers(matching: parentPred).first + } + return direct +} + +func isMultiSourceUnifiedId(_ contactId: String) throws -> Bool { + let containerPred = CNContainer.predicateForContainerOfContact(withIdentifier: contactId) + let containers = try contactStore.containers(matching: containerPred) + return containers.isEmpty +} + +func resolveAuthorizedBackings( + forContactId contactId: String, + allowedContainerIds: Set, + keysToFetch keys: [CNKeyDescriptor] +) throws -> [AuthorizedRawContact] { + let unified = try contactStore.unifiedContact( + withIdentifier: contactId, + keysToFetch: [CNContactIdentifierKey as CNKeyDescriptor] + ) + + let request = CNContactFetchRequest(keysToFetch: keys) + request.predicate = CNContact.predicateForContacts(withIdentifiers: [unified.identifier]) + request.unifyResults = false + request.mutableObjects = false + + var authorized: [AuthorizedRawContact] = [] + try contactStore.enumerateContacts(with: request) { contact, _ in + guard let account = try? resolveAccountContainer(forContactId: contact.identifier) else { return } + if allowedContainerIds.contains(account.identifier) { + authorized.append(AuthorizedRawContact(contact: contact, accountContainer: account)) + } + } + return authorized +} + +/// Validate that a contact ID is a backing ID in an allowed container. +/// Returns the resolved account container on success. +@discardableResult +func validateScopedContactAccess(id: String, allowedIds: Set) throws -> CNContainer { + guard try !isMultiSourceUnifiedId(id) else { + throw CLIError.invalidInput("Use a specific contact ID from list or search.") + } + guard let account = try resolveAccountContainer(forContactId: id) else { + throw CLIError.notFound("Contact not found: \(id)") + } + guard allowedIds.contains(account.identifier) else { + throw CLIError.accessDenied("Contact is not in your allowed accounts.") + } + return account +} + // MARK: - Commands +struct ListContainers: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "containers", + abstract: "List all contact account containers" + ) + + @OptionGroup var pimOptions: PIMOptions + + func run() async throws { + try await requestContactsAccess() + let config = pimOptions.loadConfig() + try checkContactsEnabled(config: config) + + let containers = try filteredContainers(config: config) + let result = containers.map { containerToDict($0) } + + outputJSON([ + "success": true, + "containers": result, + "count": result.count + ]) + } +} + struct ListGroups: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "groups", @@ -507,8 +656,23 @@ struct ListGroups: AsyncParsableCommand { try await requestContactsAccess() let config = pimOptions.loadConfig() try checkContactsEnabled(config: config) - - let groups = try contactStore.groups(matching: nil) + let mode = contactAccessMode(config: config) + + var groups: [CNGroup] + switch mode { + case .fullAccess: + groups = try contactStore.groups(matching: nil) + case .scopedContainers(let allowedIds): + let allGroups = try contactStore.groups(matching: nil) + groups = allGroups.filter { group in + guard let container = try? contactStore.containers( + matching: CNContainer.predicateForContainerOfGroup(withIdentifier: group.identifier) + ).first else { + return false + } + return allowedIds.contains(container.identifier) + } + } let result = groups.map { groupToDict($0) } outputJSON([ @@ -536,6 +700,7 @@ struct ListContacts: AsyncParsableCommand { try await requestContactsAccess() let config = pimOptions.loadConfig() try checkContactsEnabled(config: config) + let mode = contactAccessMode(config: config) var contacts: [CNContact] = [] @@ -548,16 +713,41 @@ struct ListContacts: AsyncParsableCommand { // Fetch contacts in group let predicate = CNContact.predicateForContactsInGroup(withIdentifier: matchedGroup.identifier) - contacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch) + + switch mode { + case .fullAccess: + contacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch) + case .scopedContainers(let allowedIds): + let containerPred = CNContainer.predicateForContainerOfGroup(withIdentifier: matchedGroup.identifier) + if let container = try contactStore.containers(matching: containerPred).first { + guard allowedIds.contains(container.identifier) else { + throw CLIError.accessDenied("Group is not in your allowed accounts.") + } + } + let request = CNContactFetchRequest(keysToFetch: keysToFetch) + request.predicate = predicate + request.unifyResults = false + request.mutableObjects = false + try contactStore.enumerateContacts(with: request) { contact, _ in + contacts.append(contact) + } + } } else { // Fetch all contacts - let request = CNContactFetchRequest(keysToFetch: keysToFetch) - request.sortOrder = .familyName - - try contactStore.enumerateContacts(with: request) { contact, stop in - contacts.append(contact) - if contacts.count >= limit { - stop.pointee = true + switch mode { + case .fullAccess: + let request = CNContactFetchRequest(keysToFetch: keysToFetch) + request.sortOrder = .familyName + try contactStore.enumerateContacts(with: request) { contact, stop in + contacts.append(contact) + if contacts.count >= limit { + stop.pointee = true + } + } + case .scopedContainers: + contacts = try fetchContactsFromAllowedContainers(config: config) + if contacts.count > limit { + contacts = Array(contacts.prefix(limit)) } } } @@ -590,41 +780,40 @@ struct SearchContacts: AsyncParsableCommand { try await requestContactsAccess() let config = pimOptions.loadConfig() try checkContactsEnabled(config: config) + let mode = contactAccessMode(config: config) - let predicate = CNContact.predicateForContacts(matchingName: query) - var contacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch) + var contacts: [CNContact] - // Also search by email and phone if name search returns few results - if contacts.count < limit { - let allContacts = try fetchAllContacts() - let queryLower = query.lowercased() - - let emailPhoneMatches = allContacts.filter { contact in - // Skip if already found by name - if contacts.contains(where: { $0.identifier == contact.identifier }) { - return false - } + switch mode { + case .fullAccess: + let predicate = CNContact.predicateForContacts(matchingName: query) + contacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch) - // Check emails - for email in contact.emailAddresses { - if (email.value as String).lowercased().contains(queryLower) { - return true - } - } + // Also search by email and phone if name search returns few results + if contacts.count < limit { + let allContacts = try fetchAllContactsUnfiltered() + contacts.append(contentsOf: searchByEmailPhone(allContacts, excluding: contacts)) + } - // Check phones (strip non-digits for comparison) - let queryDigits = query.filter { $0.isNumber } - for phone in contact.phoneNumbers { - let phoneDigits = phone.value.stringValue.filter { $0.isNumber } - if phoneDigits.contains(queryDigits) || queryDigits.contains(phoneDigits) { - return true - } + case .scopedContainers(let allowedIds): + let nameRequest = CNContactFetchRequest(keysToFetch: keysToFetch) + nameRequest.predicate = CNContact.predicateForContacts(matchingName: query) + nameRequest.unifyResults = false + nameRequest.mutableObjects = false + + var nameMatches: [CNContact] = [] + try contactStore.enumerateContacts(with: nameRequest) { contact, _ in + guard let account = try? resolveAccountContainer(forContactId: contact.identifier) else { return } + if allowedIds.contains(account.identifier) { + nameMatches.append(contact) } - - return false } + contacts = nameMatches - contacts.append(contentsOf: emailPhoneMatches) + if contacts.count < limit { + let allAllowed = try fetchContactsFromAllowedContainers(config: config) + contacts.append(contentsOf: searchByEmailPhone(allAllowed, excluding: contacts)) + } } let result = contacts.prefix(limit).map { contactToDict($0, brief: true) } @@ -637,7 +826,32 @@ struct SearchContacts: AsyncParsableCommand { ]) } - func fetchAllContacts() throws -> [CNContact] { + private func searchByEmailPhone(_ pool: [CNContact], excluding: [CNContact]) -> [CNContact] { + let queryLower = query.lowercased() + let queryDigits = query.filter { $0.isNumber } + let existingIds = Set(excluding.map { $0.identifier }) + + return pool.filter { contact in + // Skip if already found by name + if existingIds.contains(contact.identifier) { return false } + + // Check emails + for email in contact.emailAddresses { + if (email.value as String).lowercased().contains(queryLower) { return true } + } + + // Check phones (strip non-digits for comparison) + if !queryDigits.isEmpty { + for phone in contact.phoneNumbers { + let phoneDigits = phone.value.stringValue.filter { $0.isNumber } + if phoneDigits.contains(queryDigits) || queryDigits.contains(phoneDigits) { return true } + } + } + return false + } + } + + private func fetchAllContactsUnfiltered() throws -> [CNContact] { var contacts: [CNContact] = [] let request = CNContactFetchRequest(keysToFetch: keysToFetch) @@ -664,18 +878,57 @@ struct GetContact: AsyncParsableCommand { try await requestContactsAccess() let config = pimOptions.loadConfig() try checkContactsEnabled(config: config) + let mode = contactAccessMode(config: config) - let predicate = CNContact.predicateForContacts(withIdentifiers: [id]) - let contacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch) + switch mode { + case .fullAccess: + let predicate = CNContact.predicateForContacts(withIdentifiers: [id]) + let contacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch) - guard let contact = contacts.first else { - throw CLIError.notFound("Contact not found: \(id)") - } + guard let contact = contacts.first else { + throw CLIError.notFound("Contact not found: \(id)") + } - outputJSON([ - "success": true, - "contact": contactToDict(contact, brief: false) - ]) + outputJSON([ + "success": true, + "contact": contactToDict(contact, brief: false) + ]) + + case .scopedContainers(let allowedIds): + let account = try validateScopedContactAccess(id: id, allowedIds: allowedIds) + + let request = CNContactFetchRequest(keysToFetch: keysToFetch) + request.predicate = CNContact.predicateForContacts(withIdentifiers: [id]) + request.unifyResults = false + request.mutableObjects = false + var contact: CNContact? + try contactStore.enumerateContacts(with: request) { c, stop in + contact = c + stop.pointee = true + } + guard let found = contact else { + throw CLIError.notFound("Contact not found: \(id)") + } + + var contactDict = contactToDict(found, brief: false) + contactDict["sourceContainer"] = account.name + + let related = try resolveAuthorizedBackings( + forContactId: id, allowedContainerIds: allowedIds, keysToFetch: keysToFetch + ).filter { $0.contact.identifier != id } + + let relatedDicts: [[String: Any]] = related.map { arc in + var d = contactToDict(arc.contact, brief: false) + d["sourceContainer"] = arc.accountContainer.name + return d + } + + outputJSON([ + "success": true, + "contact": contactDict, + "relatedContacts": relatedDicts + ]) + } } } @@ -687,6 +940,9 @@ struct CreateContact: AsyncParsableCommand { @OptionGroup var pimOptions: PIMOptions + @Option(name: .long, help: "Target container/account name or ID") + var container: String? + // Name fields @Option(name: .long, help: "First name") var firstName: String? @@ -784,6 +1040,18 @@ struct CreateContact: AsyncParsableCommand { let config = pimOptions.loadConfig() try checkContactsEnabled(config: config) + var targetContainerId: String? = nil + if let containerHint = container { + let accounts = try allAccountContainers() + guard let matched = accounts.first(where: { $0.identifier == containerHint || $0.name.lowercased() == containerHint.lowercased() }) else { + throw CLIError.notFound("Container not found: \(containerHint)") + } + guard ItemFilter.isAllowed(name: matched.name, id: matched.identifier, config: config.contacts) else { + throw CLIError.accessDenied("Target container is not in your allowed accounts.") + } + targetContainerId = matched.identifier + } + let contact = CNMutableContact() // Name @@ -853,7 +1121,7 @@ struct CreateContact: AsyncParsableCommand { if let note = notes { contact.note = note } let saveRequest = CNSaveRequest() - saveRequest.add(contact, toContainerWithIdentifier: nil) + saveRequest.add(contact, toContainerWithIdentifier: targetContainerId) try contactStore.execute(saveRequest) outputJSON([ @@ -968,117 +1236,172 @@ struct UpdateContact: AsyncParsableCommand { try await requestContactsAccess() let config = pimOptions.loadConfig() try checkContactsEnabled(config: config) + let mode = contactAccessMode(config: config) - let maxAttempts = 3 - var attempts = 0 + switch mode { + case .fullAccess: + let maxAttempts = 3 + var attempts = 0 - while true { - attempts += 1 + while true { + attempts += 1 - let predicate = CNContact.predicateForContacts(withIdentifiers: [id]) - let contacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch) + let predicate = CNContact.predicateForContacts(withIdentifiers: [id]) + let contacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch) - guard let existingContact = contacts.first else { - throw CLIError.notFound("Contact not found: \(id)") - } + guard let existingContact = contacts.first else { + throw CLIError.notFound("Contact not found: \(id)") + } - let contact = existingContact.mutableCopy() as! CNMutableContact + let contact = existingContact.mutableCopy() as! CNMutableContact - // Name fields - if let first = firstName { contact.givenName = first } - if let last = lastName { contact.familyName = last } - if let v = middleName { contact.middleName = v } - if let v = namePrefix { contact.namePrefix = v } - if let v = nameSuffix { contact.nameSuffix = v } - if let v = nickname { contact.nickname = v } - if let v = previousFamilyName { contact.previousFamilyName = v } - - // Phonetic - if let v = phoneticGivenName { contact.phoneticGivenName = v } - if let v = phoneticMiddleName { contact.phoneticMiddleName = v } - if let v = phoneticFamilyName { contact.phoneticFamilyName = v } - if let v = phoneticOrganizationName { contact.phoneticOrganizationName = v } - - // Organization - if let org = organization { contact.organizationName = org } - if let title = jobTitle { contact.jobTitle = title } - if let dept = department { contact.departmentName = dept } - - // Contact type - if let ct = contactType?.lowercased() { - contact.contactType = ct == "organization" ? .organization : .person - } + applyContactMutations(to: contact) - // Emails (JSON array replaces all; simple --email replaces primary) - if let emailsJSON = emails { - contact.emailAddresses = try parseEmails(emailsJSON) - } else if let emailAddr = email { - if contact.emailAddresses.isEmpty { - contact.emailAddresses = [CNLabeledValue(label: CNLabelWork, value: emailAddr as NSString)] - } else { - var existing = contact.emailAddresses.map { $0.mutableCopy() as! CNLabeledValue } - existing[0] = CNLabeledValue(label: existing[0].label, value: emailAddr as NSString) - contact.emailAddresses = existing - } - } + let saveRequest = CNSaveRequest() + saveRequest.update(contact) - // Phones (JSON array replaces all; simple --phone replaces primary) - if let phonesJSON = phones { - contact.phoneNumbers = try parsePhones(phonesJSON) - } else if let phoneNum = phone { - if contact.phoneNumbers.isEmpty { - contact.phoneNumbers = [CNLabeledValue(label: CNLabelPhoneNumberMain, value: CNPhoneNumber(stringValue: phoneNum))] - } else { - var existing = contact.phoneNumbers.map { $0.mutableCopy() as! CNLabeledValue } - existing[0] = CNLabeledValue(label: existing[0].label, value: CNPhoneNumber(stringValue: phoneNum)) - contact.phoneNumbers = existing + do { + try contactStore.execute(saveRequest) + } catch { + // CoreData 134092 = NSManagedObjectMergeError (iCloud sync conflict) + // May appear at top level or nested in underlyingErrors + if isMergeConflict(error) && attempts < maxAttempts { + fputs("Warning: Merge conflict (attempt \(attempts)/\(maxAttempts)). Re-fetching and retrying...\n", stderr) + try await Task.sleep(nanoseconds: 500_000_000) // 0.5s + continue + } + throw error } + + outputJSON([ + "success": true, + "message": "Contact updated successfully", + "contact": contactToDict(contact, brief: false) + ]) + return } - // Structured arrays (replace all when provided) - if let json = addresses { contact.postalAddresses = try parseAddresses(json) } - if let json = urls { contact.urlAddresses = try parseURLs(json) } - if let json = socialProfiles { contact.socialProfiles = try parseSocialProfiles(json) } - if let json = instantMessages { contact.instantMessageAddresses = try parseInstantMessages(json) } - if let json = relations { contact.contactRelations = try parseRelations(json) } - if let json = dates { contact.dates = try parseDates(json) } - - // Birthday - if let birthdayStr = birthday { - contact.birthday = try parseBirthday(birthdayStr) + case .scopedContainers(let allowedIds): + try validateScopedContactAccess(id: id, allowedIds: allowedIds) + + let request = CNContactFetchRequest(keysToFetch: keysToFetch) + request.predicate = CNContact.predicateForContacts(withIdentifiers: [id]) + request.unifyResults = false + request.mutableObjects = false + var fetched: CNContact? + try contactStore.enumerateContacts(with: request) { c, stop in + fetched = c + stop.pointee = true + } + guard let existingContact = fetched else { + throw CLIError.notFound("Contact not found: \(id)") } - // Notes (guarded: macOS may restrict note access via TCC) - if let note = notes { - if existingContact.isKeyAvailable(CNContactNoteKey) { - contact.note = note - } else { - fputs("Warning: Cannot set notes — Contacts note access not available. Check System Settings > Privacy & Security > Contacts.\n", stderr) + let maxAttempts = 3 + var attempts = 0 + + while true { + attempts += 1 + + let contact = existingContact.mutableCopy() as! CNMutableContact + applyContactMutations(to: contact) + + let saveRequest = CNSaveRequest() + saveRequest.update(contact) + + do { + try contactStore.execute(saveRequest) + } catch { + // CoreData 134092 = NSManagedObjectMergeError (iCloud sync conflict) + // May appear at top level or nested in underlyingErrors + if isMergeConflict(error) && attempts < maxAttempts { + fputs("Warning: Merge conflict (attempt \(attempts)/\(maxAttempts)). Re-fetching and retrying...\n", stderr) + try await Task.sleep(nanoseconds: 500_000_000) // 0.5s + continue + } + throw error } + + outputJSON([ + "success": true, + "message": "Contact updated successfully", + "contact": contactToDict(contact, brief: false) + ]) + return } + } + } - let saveRequest = CNSaveRequest() - saveRequest.update(contact) - - do { - try contactStore.execute(saveRequest) - } catch { - // CoreData 134092 = NSManagedObjectMergeError (iCloud sync conflict) - // May appear at top level or nested in underlyingErrors - if isMergeConflict(error) && attempts < maxAttempts { - fputs("Warning: Merge conflict (attempt \(attempts)/\(maxAttempts)). Re-fetching and retrying...\n", stderr) - try await Task.sleep(nanoseconds: 500_000_000) // 0.5s - continue - } - throw error + private func applyContactMutations(to contact: CNMutableContact) { + // Name fields + if let first = firstName { contact.givenName = first } + if let last = lastName { contact.familyName = last } + if let v = middleName { contact.middleName = v } + if let v = namePrefix { contact.namePrefix = v } + if let v = nameSuffix { contact.nameSuffix = v } + if let v = nickname { contact.nickname = v } + if let v = previousFamilyName { contact.previousFamilyName = v } + + // Phonetic + if let v = phoneticGivenName { contact.phoneticGivenName = v } + if let v = phoneticMiddleName { contact.phoneticMiddleName = v } + if let v = phoneticFamilyName { contact.phoneticFamilyName = v } + if let v = phoneticOrganizationName { contact.phoneticOrganizationName = v } + + // Organization + if let org = organization { contact.organizationName = org } + if let title = jobTitle { contact.jobTitle = title } + if let dept = department { contact.departmentName = dept } + + // Contact type + if let ct = contactType?.lowercased() { + contact.contactType = ct == "organization" ? .organization : .person + } + + // Emails (JSON array replaces all; simple --email replaces primary) + if let emailsJSON = emails { + contact.emailAddresses = (try? parseEmails(emailsJSON)) ?? [] + } else if let emailAddr = email { + if contact.emailAddresses.isEmpty { + contact.emailAddresses = [CNLabeledValue(label: CNLabelWork, value: emailAddr as NSString)] + } else { + let first = contact.emailAddresses[0] + contact.emailAddresses[0] = CNLabeledValue(label: first.label, value: emailAddr as NSString) } + } - outputJSON([ - "success": true, - "message": "Contact updated successfully", - "contact": contactToDict(contact, brief: false) - ]) - return + // Phones (JSON array replaces all; simple --phone replaces primary) + if let phonesJSON = phones { + contact.phoneNumbers = (try? parsePhones(phonesJSON)) ?? [] + } else if let phoneNum = phone { + if contact.phoneNumbers.isEmpty { + contact.phoneNumbers = [CNLabeledValue(label: CNLabelPhoneNumberMain, value: CNPhoneNumber(stringValue: phoneNum))] + } else { + let first = contact.phoneNumbers[0] + contact.phoneNumbers[0] = CNLabeledValue(label: first.label, value: CNPhoneNumber(stringValue: phoneNum)) + } + } + + // Structured arrays (replace all when provided) + if let json = addresses { contact.postalAddresses = (try? parseAddresses(json)) ?? [] } + if let json = urls { contact.urlAddresses = (try? parseURLs(json)) ?? [] } + if let json = socialProfiles { contact.socialProfiles = (try? parseSocialProfiles(json)) ?? [] } + if let json = instantMessages { contact.instantMessageAddresses = (try? parseInstantMessages(json)) ?? [] } + if let json = relations { contact.contactRelations = (try? parseRelations(json)) ?? [] } + if let json = dates { contact.dates = (try? parseDates(json)) ?? [] } + + // Birthday + if let birthdayStr = birthday { + contact.birthday = try? parseBirthday(birthdayStr) + } + + // Notes (guarded: macOS may restrict note access via TCC) + if let note = notes { + if contact.isKeyAvailable(CNContactNoteKey) { + contact.note = note + } else { + fputs("Warning: Cannot set notes — Contacts note access not available. Check System Settings > Privacy & Security > Contacts.\n", stderr) + } } } } @@ -1099,25 +1422,58 @@ struct DeleteContact: AsyncParsableCommand { let config = pimOptions.loadConfig() try checkContactsEnabled(config: config) - let predicate = CNContact.predicateForContacts(withIdentifiers: [id]) - let contacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch) + let mode = contactAccessMode(config: config) - guard let existingContact = contacts.first else { - throw CLIError.notFound("Contact not found: \(id)") - } + switch mode { + case .fullAccess: + let predicate = CNContact.predicateForContacts(withIdentifiers: [id]) + let contacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch) - let contactInfo = contactToDict(existingContact, brief: true) - let contact = existingContact.mutableCopy() as! CNMutableContact + guard let existingContact = contacts.first else { + throw CLIError.notFound("Contact not found: \(id)") + } - let saveRequest = CNSaveRequest() - saveRequest.delete(contact) - try contactStore.execute(saveRequest) + let contactInfo = contactToDict(existingContact, brief: true) + let contact = existingContact.mutableCopy() as! CNMutableContact - outputJSON([ - "success": true, - "message": "Contact deleted successfully", - "deletedContact": contactInfo - ]) + let saveRequest = CNSaveRequest() + saveRequest.delete(contact) + try contactStore.execute(saveRequest) + + outputJSON([ + "success": true, + "message": "Contact deleted successfully", + "deletedContact": contactInfo + ]) + + case .scopedContainers(let allowedIds): + try validateScopedContactAccess(id: id, allowedIds: allowedIds) + + let request = CNContactFetchRequest(keysToFetch: keysToFetch) + request.predicate = CNContact.predicateForContacts(withIdentifiers: [id]) + request.unifyResults = false + request.mutableObjects = false + var fetched: CNContact? + try contactStore.enumerateContacts(with: request) { c, stop in + fetched = c + stop.pointee = true + } + guard let existingContact = fetched else { + throw CLIError.notFound("Contact not found: \(id)") + } + + let contactInfo = contactToDict(existingContact, brief: true) + let contact = existingContact.mutableCopy() as! CNMutableContact + let saveRequest = CNSaveRequest() + saveRequest.delete(contact) + try contactStore.execute(saveRequest) + + outputJSON([ + "success": true, + "message": "Contact deleted successfully", + "deletedContact": contactInfo + ]) + } } } diff --git a/swift/Tests/ContactsCLITests/ContainerFilterTests.swift b/swift/Tests/ContactsCLITests/ContainerFilterTests.swift new file mode 100644 index 0000000..dc6342c --- /dev/null +++ b/swift/Tests/ContactsCLITests/ContainerFilterTests.swift @@ -0,0 +1,47 @@ +import Testing +@testable import ContactsCLI +@testable import PIMConfig + +@Suite("Container Filtering") +struct ContainerFilterTests { + + @Test("contactAccessMode returns fullAccess for mode all") + func testAccessModeAllReturnsFullAccess() { + let config = PIMConfiguration(contacts: DomainFilterConfig(mode: .all)) + let mode = contactAccessMode(config: config) + guard case .fullAccess = mode else { + Issue.record("Expected .fullAccess, got \(mode)") + return + } + } + + @Test("contactAccessMode returns fullAccess for mode all even with items") + func testAccessModeAllIgnoresItems() { + let config = PIMConfiguration(contacts: DomainFilterConfig(mode: .all, items: ["Work", "Personal"])) + let mode = contactAccessMode(config: config) + guard case .fullAccess = mode else { + Issue.record("Expected .fullAccess, got \(mode)") + return + } + } + + @Test("ContactAccessMode enum cases are distinct") + func testAccessModeEnumDistinct() { + let full = ContactAccessMode.fullAccess + let scoped = ContactAccessMode.scopedContainers(Set(["container-1", "container-2"])) + + switch full { + case .fullAccess: break + case .scopedContainers: Issue.record("fullAccess matched scopedContainers") + } + + switch scoped { + case .scopedContainers(let ids): + #expect(ids.count == 2) + #expect(ids.contains("container-1")) + #expect(ids.contains("container-2")) + case .fullAccess: + Issue.record("scopedContainers matched fullAccess") + } + } +} diff --git a/swift/Tests/MailCLITests/MIMEBuilderTests.swift b/swift/Tests/MailCLITests/MIMEBuilderTests.swift index 37820eb..1f53dd7 100644 --- a/swift/Tests/MailCLITests/MIMEBuilderTests.swift +++ b/swift/Tests/MailCLITests/MIMEBuilderTests.swift @@ -1,4 +1,5 @@ import Foundation +import struct MailCLI.Attachment import Testing @testable import MailCLI @@ -257,9 +258,10 @@ struct MIMEBuilderTests { @Test("RFC 5322 date format is parseable") func testDateFormat() { + // formatRFC5322Date renders in local timezone — epoch 0 is 1969 west of UTC, 1970 at/east of UTC let s = MIMEMessage.formatRFC5322Date(Date(timeIntervalSince1970: 0)) - // Example: "Thu, 1 Jan 1970 00:00:00 +0000" - #expect(s.contains("1970")) + #expect(s.contains("1969") || s.contains("1970"), + "Expected year 1969 or 1970 in: \(s)") #expect(s.range(of: #"^[A-Z][a-z]{2}, \d{1,2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} [+-]\d{4}$"#, options: .regularExpression) != nil, "Date does not match RFC 5322 shape: \(s)") From 79807fd762afa223aa2618b400c981c296f47b45 Mon Sep 17 00:00:00 2001 From: Omar Shahine <10343873+omarshahine@users.noreply.github.com> Date: Fri, 15 May 2026 04:45:33 +0000 Subject: [PATCH 2/2] fix(contacts): address codex review findings on scoped mode - CreateContact: when scoped mode active and --container omitted, resolve the system default container and validate it against the allowlist; auto-pick if exactly one allowed; otherwise require --container. Previously the system default could silently bypass the allowlist (e.g. iCloud default while only Exchange allowed). - UpdateContact (scoped path): move CNContactFetchRequest inside the merge-conflict retry loop. Previously the warning said "Re-fetching and retrying..." but the retry actually reused the stale fetch. - applyContactMutations: propagate parse errors instead of swallowing with try?. Invalid JSON in --emails / --phones / --addresses / --urls / --social-profiles / --instant-messages / --relations / --dates / --birthday now fails the update instead of writing an empty array (data loss). Co-Authored-By: Claude Opus 4.7 (1M context) --- swift/Sources/ContactsCLI/ContactsCLI.swift | 62 ++++++++++++--------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/swift/Sources/ContactsCLI/ContactsCLI.swift b/swift/Sources/ContactsCLI/ContactsCLI.swift index d9c3e92..cfe8e1e 100644 --- a/swift/Sources/ContactsCLI/ContactsCLI.swift +++ b/swift/Sources/ContactsCLI/ContactsCLI.swift @@ -1050,6 +1050,18 @@ struct CreateContact: AsyncParsableCommand { throw CLIError.accessDenied("Target container is not in your allowed accounts.") } targetContainerId = matched.identifier + } else if case .scopedContainers(let allowedIds) = contactAccessMode(config: config) { + // Without --container, Contacts saves to the system default account. + // In scoped mode that default may be disallowed (e.g. iCloud while only Exchange is allowed), + // which would bypass the allowlist. Resolve and validate explicitly. + let defaultId = contactStore.defaultContainerIdentifier() + if allowedIds.contains(defaultId) { + targetContainerId = defaultId + } else if allowedIds.count == 1, let onlyAllowed = allowedIds.first { + targetContainerId = onlyAllowed + } else { + throw CLIError.invalidInput("System default contacts account is not in your allowed accounts. Pass --container explicitly.") + } } let contact = CNMutableContact() @@ -1255,7 +1267,7 @@ struct UpdateContact: AsyncParsableCommand { let contact = existingContact.mutableCopy() as! CNMutableContact - applyContactMutations(to: contact) + try applyContactMutations(to: contact) let saveRequest = CNSaveRequest() saveRequest.update(contact) @@ -1284,27 +1296,27 @@ struct UpdateContact: AsyncParsableCommand { case .scopedContainers(let allowedIds): try validateScopedContactAccess(id: id, allowedIds: allowedIds) - let request = CNContactFetchRequest(keysToFetch: keysToFetch) - request.predicate = CNContact.predicateForContacts(withIdentifiers: [id]) - request.unifyResults = false - request.mutableObjects = false - var fetched: CNContact? - try contactStore.enumerateContacts(with: request) { c, stop in - fetched = c - stop.pointee = true - } - guard let existingContact = fetched else { - throw CLIError.notFound("Contact not found: \(id)") - } - let maxAttempts = 3 var attempts = 0 while true { attempts += 1 + let request = CNContactFetchRequest(keysToFetch: keysToFetch) + request.predicate = CNContact.predicateForContacts(withIdentifiers: [id]) + request.unifyResults = false + request.mutableObjects = false + var fetched: CNContact? + try contactStore.enumerateContacts(with: request) { c, stop in + fetched = c + stop.pointee = true + } + guard let existingContact = fetched else { + throw CLIError.notFound("Contact not found: \(id)") + } + let contact = existingContact.mutableCopy() as! CNMutableContact - applyContactMutations(to: contact) + try applyContactMutations(to: contact) let saveRequest = CNSaveRequest() saveRequest.update(contact) @@ -1332,7 +1344,7 @@ struct UpdateContact: AsyncParsableCommand { } } - private func applyContactMutations(to contact: CNMutableContact) { + private func applyContactMutations(to contact: CNMutableContact) throws { // Name fields if let first = firstName { contact.givenName = first } if let last = lastName { contact.familyName = last } @@ -1360,7 +1372,7 @@ struct UpdateContact: AsyncParsableCommand { // Emails (JSON array replaces all; simple --email replaces primary) if let emailsJSON = emails { - contact.emailAddresses = (try? parseEmails(emailsJSON)) ?? [] + contact.emailAddresses = try parseEmails(emailsJSON) } else if let emailAddr = email { if contact.emailAddresses.isEmpty { contact.emailAddresses = [CNLabeledValue(label: CNLabelWork, value: emailAddr as NSString)] @@ -1372,7 +1384,7 @@ struct UpdateContact: AsyncParsableCommand { // Phones (JSON array replaces all; simple --phone replaces primary) if let phonesJSON = phones { - contact.phoneNumbers = (try? parsePhones(phonesJSON)) ?? [] + contact.phoneNumbers = try parsePhones(phonesJSON) } else if let phoneNum = phone { if contact.phoneNumbers.isEmpty { contact.phoneNumbers = [CNLabeledValue(label: CNLabelPhoneNumberMain, value: CNPhoneNumber(stringValue: phoneNum))] @@ -1383,16 +1395,16 @@ struct UpdateContact: AsyncParsableCommand { } // Structured arrays (replace all when provided) - if let json = addresses { contact.postalAddresses = (try? parseAddresses(json)) ?? [] } - if let json = urls { contact.urlAddresses = (try? parseURLs(json)) ?? [] } - if let json = socialProfiles { contact.socialProfiles = (try? parseSocialProfiles(json)) ?? [] } - if let json = instantMessages { contact.instantMessageAddresses = (try? parseInstantMessages(json)) ?? [] } - if let json = relations { contact.contactRelations = (try? parseRelations(json)) ?? [] } - if let json = dates { contact.dates = (try? parseDates(json)) ?? [] } + if let json = addresses { contact.postalAddresses = try parseAddresses(json) } + if let json = urls { contact.urlAddresses = try parseURLs(json) } + if let json = socialProfiles { contact.socialProfiles = try parseSocialProfiles(json) } + if let json = instantMessages { contact.instantMessageAddresses = try parseInstantMessages(json) } + if let json = relations { contact.contactRelations = try parseRelations(json) } + if let json = dates { contact.dates = try parseDates(json) } // Birthday if let birthdayStr = birthday { - contact.birthday = try? parseBirthday(birthdayStr) + contact.birthday = try parseBirthday(birthdayStr) } // Notes (guarded: macOS may restrict note access via TCC)