inbound-mime-repair.test.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import assert from 'node:assert/strict';
  2. import { mkdtempSync } from 'node:fs';
  3. import { tmpdir } from 'node:os';
  4. import path from 'node:path';
  5. import { test } from 'node:test';
  6. import {
  7. createDomain,
  8. createInboundMailbox,
  9. createInboundMessage,
  10. createUser,
  11. getInboundMessage,
  12. initDatabase
  13. } from '../src/db.js';
  14. import { repairStoredInboundMime } from '../src/inbound-mime-repair.js';
  15. test('repairStoredInboundMime reparses legacy APPEND records and is idempotent', async () => {
  16. initDatabase(mkdtempSync(path.join(tmpdir(), 'mailhub-inbound-mime-repair-')), 'repair-secret');
  17. const user = createUser({ username: 'repair-user', email: 'repair@example.com', password: 'password123' });
  18. createDomain(user.id, {
  19. domain: 'repair.example',
  20. selector: 'mh',
  21. verificationToken: 'verify',
  22. dkimPublic: 'public',
  23. dkimPrivate: 'private',
  24. senderHost: 'mail.repair.example',
  25. sendingIp: '192.0.2.40',
  26. spfExtra: '',
  27. dmarcPolicy: 'none',
  28. dmarcRua: ''
  29. });
  30. const mailbox = createInboundMailbox(user.id, {
  31. address: 'admin@repair.example',
  32. password: 'mailbox-pass-123'
  33. });
  34. const rawMessage = [
  35. 'From: Admin <admin@repair.example>',
  36. 'To: Bob <bob@example.net>',
  37. 'Subject: =?UTF-8?Q?=E6=A0=B8=E4=BA=91?=',
  38. ' =?UTF-8?Q?=E8=AE=A1=E7=AE=97?=',
  39. 'Message-ID: <legacy-append@repair.example>',
  40. 'MIME-Version: 1.0',
  41. 'Content-Type: multipart/alternative; boundary="legacy-boundary"',
  42. '',
  43. '--legacy-boundary',
  44. 'Content-Type: text/plain; charset=UTF-8',
  45. 'Content-Transfer-Encoding: base64',
  46. '',
  47. Buffer.from('修复后的正文', 'utf8').toString('base64'),
  48. '--legacy-boundary',
  49. 'Content-Type: text/html; charset=UTF-8',
  50. '',
  51. '<p>Repaired HTML body.</p>',
  52. '--legacy-boundary--',
  53. ''
  54. ].join('\r\n');
  55. const legacyMessage = createInboundMessage(mailbox, {
  56. folder: 'Sent',
  57. sender: 'admin@repair.example',
  58. recipients: ['bob@example.net'],
  59. subject: '=?UTF-8?Q?=E6=A0=B8=E4=BA=91?= =?UTF-8?Q?=E8=AE=A1=E7=AE=97?=',
  60. messageId: '<legacy-append@repair.example>',
  61. rawMessage,
  62. textBody: rawMessage.split('\r\n\r\n').slice(1).join('\r\n\r\n')
  63. });
  64. const attachmentRaw = [
  65. 'From: Admin <admin@repair.example>',
  66. 'To: Bob <bob@example.net>',
  67. 'Subject: Attached notes',
  68. 'Content-Type: multipart/mixed; boundary="attachment-boundary"',
  69. '',
  70. '--attachment-boundary',
  71. 'Content-Type: text/plain; charset=UTF-8',
  72. 'Content-Disposition: attachment; filename="notes.txt"',
  73. '',
  74. 'Attachment content',
  75. '--attachment-boundary--',
  76. ''
  77. ].join('\r\n');
  78. const attachmentMessage = createInboundMessage(mailbox, {
  79. folder: 'Sent',
  80. sender: 'admin@repair.example',
  81. recipients: ['bob@example.net'],
  82. subject: 'Attached notes',
  83. rawMessage: attachmentRaw,
  84. textBody: attachmentRaw.split('\r\n\r\n').slice(1).join('\r\n\r\n')
  85. });
  86. const legitimateMessage = createInboundMessage(mailbox, {
  87. folder: 'Sent',
  88. sender: 'admin@repair.example',
  89. recipients: ['bob@example.net'],
  90. subject: 'Legitimate body',
  91. rawMessage: [
  92. 'From: Admin <admin@repair.example>',
  93. 'To: Bob <bob@example.net>',
  94. 'Subject: Legitimate body',
  95. 'Content-Type: multipart/alternative; boundary="legitimate-boundary"',
  96. '',
  97. '--legitimate-boundary',
  98. 'Content-Type: text/plain; charset=UTF-8',
  99. '',
  100. '-- signature',
  101. '--legitimate-boundary--',
  102. ''
  103. ].join('\r\n'),
  104. textBody: '-- signature'
  105. });
  106. assert.deepEqual(await repairStoredInboundMime(), { repaired: 2, failed: 0 });
  107. const repaired = getInboundMessage(user.id, legacyMessage.id);
  108. assert.equal(repaired.subject, '核云计算');
  109. assert.equal(repaired.textBody, '修复后的正文');
  110. assert.match(repaired.htmlBody, /Repaired HTML body/);
  111. assert.equal(repaired.preview, '修复后的正文');
  112. assert.match(repaired.rawMessage, /Subject: =\?UTF-8\?Q\?/);
  113. const repairedAttachment = getInboundMessage(user.id, attachmentMessage.id);
  114. assert.equal(repairedAttachment.textBody, '');
  115. assert.equal(repairedAttachment.preview, '');
  116. assert.equal(getInboundMessage(user.id, legitimateMessage.id).textBody, '-- signature');
  117. assert.deepEqual(await repairStoredInboundMime(), { repaired: 0, failed: 0 });
  118. });