imapflow 1.7.1 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "1.7.1"
2
+ ".": "1.7.2"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.7.2](https://github.com/postalsys/imapflow/compare/v1.7.1...v1.7.2) (2026-08-21)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **envelope:** stop inventing an address from a NIL host field ([253a7b0](https://github.com/postalsys/imapflow/commit/253a7b0747ebe3c59fb177d46eb9843da6b5e91e))
9
+ * **types:** accept string[] paths in status, getQuota, append, copy and move ([92f3607](https://github.com/postalsys/imapflow/commit/92f360749fd9518532f0d908bf246fb9402f2eaa)), closes [#382](https://github.com/postalsys/imapflow/issues/382)
10
+
3
11
  ## [1.7.1](https://github.com/postalsys/imapflow/compare/v1.7.0...v1.7.1) (2026-08-14)
4
12
 
5
13
 
@@ -787,7 +787,7 @@ export class ImapFlow extends EventEmitter {
787
787
  close(): void;
788
788
 
789
789
  /** Returns current quota */
790
- getQuota(path?: string): Promise<QuotaResponse | false>;
790
+ getQuota(path?: string | string[]): Promise<QuotaResponse | false>;
791
791
 
792
792
  /** Lists available mailboxes as an Array */
793
793
  list(options?: ListOptions): Promise<ListResponse[]>;
@@ -821,7 +821,7 @@ export class ImapFlow extends EventEmitter {
821
821
 
822
822
  /** Requests the status of the indicated mailbox */
823
823
  status(
824
- path: string,
824
+ path: string | string[],
825
825
  query: {
826
826
  messages?: boolean;
827
827
  recent?: boolean;
@@ -855,13 +855,21 @@ export class ImapFlow extends EventEmitter {
855
855
  messageDelete(range: SequenceString | number[] | SearchObject, options?: { uid?: boolean }): Promise<boolean>;
856
856
 
857
857
  /** Appends a new message to a mailbox */
858
- append(path: string, content: string | Buffer, flags?: string[], idate?: Date | string): Promise<AppendResponseObject | false>;
858
+ append(path: string | string[], content: string | Buffer, flags?: string[], idate?: Date | string): Promise<AppendResponseObject | false>;
859
859
 
860
860
  /** Copies messages from current mailbox to destination mailbox */
861
- messageCopy(range: SequenceString | number[] | SearchObject, destination: string, options?: { uid?: boolean }): Promise<CopyResponseObject | false>;
861
+ messageCopy(
862
+ range: SequenceString | number[] | SearchObject,
863
+ destination: string | string[],
864
+ options?: { uid?: boolean }
865
+ ): Promise<CopyResponseObject | false>;
862
866
 
863
867
  /** Moves messages from current mailbox to destination mailbox */
864
- messageMove(range: SequenceString | number[] | SearchObject, destination: string, options?: { uid?: boolean }): Promise<CopyResponseObject | false>;
868
+ messageMove(
869
+ range: SequenceString | number[] | SearchObject,
870
+ destination: string | string[],
871
+ options?: { uid?: boolean }
872
+ ): Promise<CopyResponseObject | false>;
865
873
 
866
874
  /** Search messages from the currently opened mailbox — returns number[] (backward-compatible) */
867
875
  search(query: SearchObject, options?: { uid?: boolean }): Promise<number[] | false>;
package/lib/imap-flow.js CHANGED
@@ -2848,7 +2848,7 @@ class ImapFlow extends EventEmitter {
2848
2848
  /**
2849
2849
  * Returns current quota
2850
2850
  *
2851
- * @param {String} [path] Optional mailbox path if you want to check quota for specific folder
2851
+ * @param {string|array} [path] Optional mailbox path if you want to check quota for specific folder. If value is an array then it is joined using current delimiter symbols. Namespace prefix is added automatically if required.
2852
2852
  * @returns {Promise<QuotaResponse|Boolean>} Quota information or `false` if QUOTA extension is not supported or requested path does not exist
2853
2853
  *
2854
2854
  * @example
@@ -3101,7 +3101,7 @@ class ImapFlow extends EventEmitter {
3101
3101
  /**
3102
3102
  * Requests the status of the indicated mailbox. Only requested status values will be returned.
3103
3103
  *
3104
- * @param {String} path mailbox path to check for (unicode string)
3104
+ * @param {string|array} path mailbox path to check for (unicode string). If value is an array then it is joined using current delimiter symbols. Namespace prefix is added automatically if required.
3105
3105
  * @param {Object} query defines requested status items
3106
3106
  * @param {Boolean} query.messages if `true` request count of messages
3107
3107
  * @param {Boolean} query.recent if `true` request count of messages with \\Recent tag
@@ -3389,7 +3389,7 @@ class ImapFlow extends EventEmitter {
3389
3389
  /**
3390
3390
  * Appends a new message to a mailbox
3391
3391
  *
3392
- * @param {String} path Mailbox path to upload the message to (unicode string)
3392
+ * @param {string|array} path Mailbox path to upload the message to (unicode string). If value is an array then it is joined using current delimiter symbols. Namespace prefix is added automatically if required.
3393
3393
  * @param {string|Buffer} content RFC822 formatted email message
3394
3394
  * @param {string[]} [flags] an array of flags to be set for the uploaded message
3395
3395
  * @param {Date|string} [idate=now] internal date to be set for the message
@@ -3415,7 +3415,7 @@ class ImapFlow extends EventEmitter {
3415
3415
  * Copies messages from current mailbox to destination mailbox
3416
3416
  *
3417
3417
  * @param {SequenceString | Number[] | SearchObject} range Range of messages to copy
3418
- * @param {String} destination Mailbox path to copy the messages to
3418
+ * @param {string|array} destination Mailbox path to copy the messages to. If value is an array then it is joined using current delimiter symbols. Namespace prefix is added automatically if required.
3419
3419
  * @param {Object} [options]
3420
3420
  * @param {Boolean} [options.uid] If `true` then uses UID {@link SequenceString} instead of sequence numbers
3421
3421
  * @returns {Promise<CopyResponseObject>} info about copies messages
@@ -3439,7 +3439,7 @@ class ImapFlow extends EventEmitter {
3439
3439
  * Moves messages from current mailbox to destination mailbox
3440
3440
  *
3441
3441
  * @param {SequenceString | Number[] | SearchObject} range Range of messages to move
3442
- * @param {String} destination Mailbox path to move the messages to
3442
+ * @param {string|array} destination Mailbox path to move the messages to. If value is an array then it is joined using current delimiter symbols. Namespace prefix is added automatically if required.
3443
3443
  * @param {Object} [options]
3444
3444
  * @param {Boolean} [options.uid] If `true` then uses UID {@link SequenceString} instead of sequence numbers
3445
3445
  * @returns {Promise<CopyResponseObject>} info about moved messages
package/lib/tools.js CHANGED
@@ -764,6 +764,17 @@ const tools = {
764
764
  return name;
765
765
  },
766
766
 
767
+ /**
768
+ * Decodes an ENVELOPE text field for display: encoded words first, then the
769
+ * surrounding quotes some servers leave in place.
770
+ *
771
+ * @param {String} value - Raw field value from an ENVELOPE response
772
+ * @returns {String} Decoded, unquoted text
773
+ */
774
+ decodeText(value) {
775
+ return tools.processName(libmime.decodeWords(value));
776
+ },
777
+
767
778
  /**
768
779
  * Parses a raw IMAP ENVELOPE response into a structured envelope object.
769
780
  *
@@ -795,14 +806,25 @@ const tools = {
795
806
  // throwing on the dereference and dropping the message
796
807
  return false;
797
808
  }
798
- let address = (getStrValue(addr[2]) || '') + '@' + (getStrValue(addr[3]) || '');
799
- if (address === '@') {
800
- address = '';
809
+
810
+ let name = tools.decodeText(getStrValue(addr[0]));
811
+ let mailbox = getStrValue(addr[2]) || '';
812
+ let host = getStrValue(addr[3]) || '';
813
+
814
+ if (!host) {
815
+ // RFC 9051 7.5.2: a NIL host field marks RFC 5322 group syntax, it is not
816
+ // an empty domain. A non-NIL mailbox then holds the group name phrase, a
817
+ // NIL one closes the group. Joining the fields anyway would invent an
818
+ // address that never appeared in the message, eg. "undisclosed-recipients@",
819
+ // so surface the group name as a display name and leave the address empty.
820
+ // End-of-group markers carry neither and the filter below drops them.
821
+ // The mirror case, a NIL mailbox with a host, is left alone on purpose:
822
+ // the grammar gives it no meaning, so a server sending it is simply
823
+ // malformed rather than signalling anything we could act on.
824
+ return { name: name || (mailbox && tools.decodeText(mailbox)), address: '' };
801
825
  }
802
- return {
803
- name: tools.processName(libmime.decodeWords(getStrValue(addr[0]))),
804
- address
805
- };
826
+
827
+ return { name, address: `${mailbox}@${host}` };
806
828
  })
807
829
  .filter(addr => addr && (addr.name || addr.address));
808
830
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "lib/imap-flow.js",
6
6
  "types": "lib/imap-flow.d.ts",
@@ -32,7 +32,7 @@
32
32
  "@eslint/js": "10.0.1",
33
33
  "@types/node": "26.2.0",
34
34
  "c8": "12.0.0",
35
- "eslint": "10.8.1",
35
+ "eslint": "10.9.0",
36
36
  "eslint-config-nodemailer": "1.2.0",
37
37
  "eslint-config-prettier": "10.1.8",
38
38
  "grunt": "1.6.3",
@@ -716,6 +716,25 @@ module.exports['Tools: processName with short quoted'] = test => {
716
716
  test.done();
717
717
  };
718
718
 
719
+ // ============================================
720
+ // decodeText tests
721
+ // ============================================
722
+
723
+ module.exports['Tools: decodeText decodes encoded words and strips quotes'] = test => {
724
+ test.equal(tools.decodeText('=?utf-8?Q?T=C3=B5nu?='), 'Tõnu');
725
+ test.equal(tools.decodeText('"=?utf-8?Q?T=C3=B5nu?="'), 'Tõnu');
726
+ test.equal(tools.decodeText('Plain Name'), 'Plain Name');
727
+ test.done();
728
+ };
729
+
730
+ module.exports['Tools: decodeText tolerates missing values'] = test => {
731
+ // getStrValue returns false for a NIL envelope field
732
+ test.equal(tools.decodeText(false), '');
733
+ test.equal(tools.decodeText(null), '');
734
+ test.equal(tools.decodeText(undefined), '');
735
+ test.done();
736
+ };
737
+
719
738
  // ============================================
720
739
  // getFolderTree tests
721
740
  // ============================================
@@ -885,6 +904,100 @@ module.exports['Tools: parseEnvelope with empty address parts'] = test => {
885
904
  test.done();
886
905
  };
887
906
 
907
+ module.exports['Tools: parseEnvelope keeps group syntax out of the address'] = test => {
908
+ // RFC 9051 7.5.2: a NIL host marks group syntax, so "undisclosed-recipients:;" must
909
+ // not turn into the invented address "undisclosed-recipients@"
910
+ let entry = [
911
+ null, // date
912
+ null, // subject
913
+ [], // from
914
+ [], // sender
915
+ [], // reply-to
916
+ [
917
+ [null, null, { value: 'undisclosed-recipients' }, null], // start of group
918
+ [null, null, null, null] // end of group
919
+ ], // to
920
+ [], // cc
921
+ [], // bcc
922
+ null, // in-reply-to
923
+ null // message-id
924
+ ];
925
+
926
+ let result = tools.parseEnvelope(entry);
927
+ // The end-of-group marker carries neither name nor address and is dropped
928
+ test.deepEqual(result.to, [{ name: 'undisclosed-recipients', address: '' }]);
929
+ test.done();
930
+ };
931
+
932
+ module.exports['Tools: parseEnvelope keeps group members alongside the markers'] = test => {
933
+ let entry = [
934
+ null, // date
935
+ null, // subject
936
+ [], // from
937
+ [], // sender
938
+ [], // reply-to
939
+ [
940
+ [null, null, { value: 'Team' }, null], // start of group
941
+ [{ value: 'Member One' }, null, { value: 'one' }, { value: 'example.com' }],
942
+ [{ value: 'Member Two' }, null, { value: 'two' }, { value: 'example.com' }],
943
+ [null, null, null, null] // end of group
944
+ ], // to
945
+ [], // cc
946
+ [], // bcc
947
+ null, // in-reply-to
948
+ null // message-id
949
+ ];
950
+
951
+ let result = tools.parseEnvelope(entry);
952
+ test.deepEqual(result.to, [
953
+ { name: 'Team', address: '' },
954
+ { name: 'Member One', address: 'one@example.com' },
955
+ { name: 'Member Two', address: 'two@example.com' }
956
+ ]);
957
+ test.done();
958
+ };
959
+
960
+ module.exports['Tools: parseEnvelope does not join a NIL host onto a mailbox'] = test => {
961
+ // Some servers parse a malformed header such as
962
+ // "To: user@example.com user@example.com" into a personal name plus a mailbox
963
+ // with a NIL host. Joining those produced the invalid address "example.com@".
964
+ let entry = [
965
+ null, // date
966
+ null, // subject
967
+ [], // from
968
+ [], // sender
969
+ [], // reply-to
970
+ [[{ value: 'user@example.com user@' }, null, { value: 'example.com' }, null]], // to
971
+ [], // cc
972
+ [], // bcc
973
+ null, // in-reply-to
974
+ null // message-id
975
+ ];
976
+
977
+ let result = tools.parseEnvelope(entry);
978
+ test.deepEqual(result.to, [{ name: 'user@example.com user@', address: '' }]);
979
+ test.done();
980
+ };
981
+
982
+ module.exports['Tools: parseEnvelope decodes an encoded group name'] = test => {
983
+ let entry = [
984
+ null, // date
985
+ null, // subject
986
+ [], // from
987
+ [], // sender
988
+ [], // reply-to
989
+ [[null, null, { value: '=?utf-8?Q?T=C3=B5ny?=' }, null]], // to
990
+ [], // cc
991
+ [], // bcc
992
+ null, // in-reply-to
993
+ null // message-id
994
+ ];
995
+
996
+ let result = tools.parseEnvelope(entry);
997
+ test.deepEqual(result.to, [{ name: 'Tõny', address: '' }]);
998
+ test.done();
999
+ };
1000
+
888
1001
  // ============================================
889
1002
  // getStructuredParams tests
890
1003
  // ============================================