imapflow 1.2.5 → 1.2.7

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.2.5"
2
+ ".": "1.2.7"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.7](https://github.com/postalsys/imapflow/compare/v1.2.6...v1.2.7) (2026-01-26)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * add _destroy() methods to Transform streams for proper cleanup ([ed78912](https://github.com/postalsys/imapflow/commit/ed78912143d3613a7687a2bae880edd9051355cc))
9
+
10
+ ## [1.2.6](https://github.com/postalsys/imapflow/compare/v1.2.5...v1.2.6) (2026-01-12)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * prevent memory leaks in socket and event handling ([2dfa6f3](https://github.com/postalsys/imapflow/commit/2dfa6f3d77ccdf31244752d669ab694f1c157b4c))
16
+ * restore isClosed check and error logging in initialPREAUTH ([ae2ebcf](https://github.com/postalsys/imapflow/commit/ae2ebcff5a65aa870b1af70726f7c5c6ac54395e))
17
+
3
18
  ## [1.2.5](https://github.com/postalsys/imapflow/compare/v1.2.4...v1.2.5) (2026-01-11)
4
19
 
5
20
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { getStatusCode, formatFlag, canUseFlag, formatDateTime, normalizePath, encodePath, comparePaths, getErrorText } = require('../tools.js');
3
+ const { formatFlag, canUseFlag, formatDateTime, normalizePath, encodePath, comparePaths, enhanceCommandError } = require('../tools.js');
4
4
 
5
5
  // Appends a message to a mailbox
6
6
  module.exports = async (connection, destination, content, flags, idate) => {
@@ -139,12 +139,7 @@ module.exports = async (connection, destination, content, flags, idate) => {
139
139
 
140
140
  return map;
141
141
  } catch (err) {
142
- let errorCode = getStatusCode(err.response);
143
- if (errorCode) {
144
- err.serverResponseCode = errorCode;
145
- }
146
- err.response = await getErrorText(err.response);
147
-
142
+ await enhanceCommandError(err);
148
143
  connection.log.warn({ err, cid: connection.id });
149
144
  throw err;
150
145
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { getStatusCode, normalizePath, encodePath, expandRange, getErrorText } = require('../tools.js');
3
+ const { normalizePath, encodePath, expandRange, enhanceCommandError } = require('../tools.js');
4
4
 
5
5
  // Copies messages from current mailbox to some other mailbox
6
6
  module.exports = async (connection, range, destination, options) => {
@@ -44,12 +44,7 @@ module.exports = async (connection, range, destination, options) => {
44
44
 
45
45
  return map;
46
46
  } catch (err) {
47
- let errorCode = getStatusCode(err.response);
48
- if (errorCode) {
49
- err.serverResponseCode = errorCode;
50
- }
51
- err.response = await getErrorText(err.response);
52
-
47
+ await enhanceCommandError(err);
53
48
  connection.log.warn({ err, cid: connection.id });
54
49
  return false;
55
50
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
3
+ const { encodePath, normalizePath, getStatusCode, enhanceCommandError } = require('../tools.js');
4
4
 
5
5
  // Creates a new mailbox
6
6
  module.exports = async (connection, path) => {
@@ -61,24 +61,15 @@ module.exports = async (connection, path) => {
61
61
  return map;
62
62
  } catch (err) {
63
63
  let errorCode = getStatusCode(err.response);
64
- if (errorCode) {
65
- err.serverResponseCode = errorCode;
66
- }
67
- err.response = await getErrorText(err.response);
68
-
69
- switch (errorCode) {
70
- case 'ALREADYEXISTS':
71
- // no need to do anything, mailbox already exists
72
- return {
73
- path,
74
- created: false
75
- };
76
- }
77
-
78
- if (errorCode) {
79
- err.serverResponseCode = errorCode;
64
+ if (errorCode === 'ALREADYEXISTS') {
65
+ // no need to do anything, mailbox already exists
66
+ return {
67
+ path,
68
+ created: false
69
+ };
80
70
  }
81
71
 
72
+ await enhanceCommandError(err);
82
73
  connection.log.warn({ err, cid: connection.id });
83
74
  throw err;
84
75
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
3
+ const { encodePath, normalizePath, enhanceCommandError } = require('../tools.js');
4
4
 
5
5
  // Deletes an existing mailbox
6
6
  module.exports = async (connection, path) => {
@@ -24,12 +24,7 @@ module.exports = async (connection, path) => {
24
24
  response.next();
25
25
  return map;
26
26
  } catch (err) {
27
- let errorCode = getStatusCode(err.response);
28
- if (errorCode) {
29
- err.serverResponseCode = errorCode;
30
- }
31
- err.response = await getErrorText(err.response);
32
-
27
+ await enhanceCommandError(err);
33
28
  connection.log.warn({ err, cid: connection.id });
34
29
  throw err;
35
30
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { getStatusCode, getErrorText } = require('../tools.js');
3
+ const { enhanceCommandError } = require('../tools.js');
4
4
 
5
5
  // Deletes specified messages
6
6
  module.exports = async (connection, range, options) => {
@@ -34,12 +34,7 @@ module.exports = async (connection, range, options) => {
34
34
  response.next();
35
35
  return true;
36
36
  } catch (err) {
37
- let errorCode = getStatusCode(err.response);
38
- if (errorCode) {
39
- err.serverResponseCode = errorCode;
40
- }
41
- err.response = await getErrorText(err.response);
42
-
37
+ await enhanceCommandError(err);
43
38
  connection.log.warn({ err, cid: connection.id });
44
39
  return false;
45
40
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { getStatusCode, normalizePath, encodePath, expandRange, getErrorText } = require('../tools.js');
3
+ const { normalizePath, encodePath, expandRange, enhanceCommandError } = require('../tools.js');
4
4
 
5
5
  // Moves messages from current mailbox to some other mailbox
6
6
  module.exports = async (connection, range, destination, options) => {
@@ -60,12 +60,7 @@ module.exports = async (connection, range, destination, options) => {
60
60
  checkMoveInfo(response.response);
61
61
  return map;
62
62
  } catch (err) {
63
- let errorCode = getStatusCode(err.response);
64
- if (errorCode) {
65
- err.serverResponseCode = errorCode;
66
- }
67
- err.response = await getErrorText(err.response);
68
-
63
+ await enhanceCommandError(err);
69
64
  connection.log.warn({ err, cid: connection.id });
70
65
  return false;
71
66
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { encodePath, getStatusCode, normalizePath, getErrorText } = require('../tools.js');
3
+ const { encodePath, normalizePath, enhanceCommandError } = require('../tools.js');
4
4
 
5
5
  // Requests quota information for a mailbox
6
6
  module.exports = async (connection, path) => {
@@ -95,12 +95,7 @@ module.exports = async (connection, path) => {
95
95
 
96
96
  return map;
97
97
  } catch (err) {
98
- let errorCode = getStatusCode(err.response);
99
- if (errorCode) {
100
- err.serverResponseCode = errorCode;
101
- }
102
- err.response = await getErrorText(err.response);
103
-
98
+ await enhanceCommandError(err);
104
99
  connection.log.warn({ err, cid: connection.id });
105
100
  return false;
106
101
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
3
+ const { encodePath, normalizePath, enhanceCommandError } = require('../tools.js');
4
4
 
5
5
  // Renames existing mailbox
6
6
  module.exports = async (connection, path, newPath) => {
@@ -29,12 +29,7 @@ module.exports = async (connection, path, newPath) => {
29
29
  response.next();
30
30
  return map;
31
31
  } catch (err) {
32
- let errorCode = getStatusCode(err.response);
33
- if (errorCode) {
34
- err.serverResponseCode = errorCode;
35
- }
36
- err.response = await getErrorText(err.response);
37
-
32
+ await enhanceCommandError(err);
38
33
  connection.log.warn({ err, cid: connection.id });
39
34
  throw err;
40
35
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { getStatusCode, getErrorText } = require('../tools.js');
3
+ const { enhanceCommandError } = require('../tools.js');
4
4
  const { searchCompiler } = require('../search-compiler.js');
5
5
 
6
6
  // Updates flags for a message
@@ -43,12 +43,7 @@ module.exports = async (connection, query, options) => {
43
43
  response.next();
44
44
  return Array.from(results).sort((a, b) => a - b);
45
45
  } catch (err) {
46
- let errorCode = getStatusCode(err.response);
47
- if (errorCode) {
48
- err.serverResponseCode = errorCode;
49
- }
50
- err.response = await getErrorText(err.response);
51
-
46
+ await enhanceCommandError(err);
52
47
  connection.log.warn({ err, cid: connection.id });
53
48
  return false;
54
49
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
3
+ const { encodePath, normalizePath, enhanceCommandError } = require('../tools.js');
4
4
 
5
5
  // Selects a mailbox
6
6
  module.exports = async (connection, path, options) => {
@@ -194,11 +194,7 @@ module.exports = async (connection, path, options) => {
194
194
  response.next();
195
195
  return map;
196
196
  } catch (err) {
197
- let errorCode = getStatusCode(err.response);
198
- if (errorCode) {
199
- err.serverResponseCode = errorCode;
200
- }
201
- err.response = await getErrorText(err.response);
197
+ await enhanceCommandError(err);
202
198
 
203
199
  if (connection.state === connection.states.SELECTED) {
204
200
  // reset selected state
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { getStatusCode, formatFlag, canUseFlag, getErrorText } = require('../tools.js');
3
+ const { formatFlag, canUseFlag, enhanceCommandError } = require('../tools.js');
4
4
 
5
5
  // Updates flags for a message
6
6
  module.exports = async (connection, range, flags, options) => {
@@ -72,12 +72,7 @@ module.exports = async (connection, range, flags, options) => {
72
72
  response.next();
73
73
  return true;
74
74
  } catch (err) {
75
- let errorCode = getStatusCode(err.response);
76
- if (errorCode) {
77
- err.serverResponseCode = errorCode;
78
- }
79
- err.response = await getErrorText(err.response);
80
-
75
+ await enhanceCommandError(err);
81
76
  connection.log.warn({ err, cid: connection.id });
82
77
  return false;
83
78
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
3
+ const { encodePath, normalizePath, enhanceCommandError } = require('../tools.js');
4
4
 
5
5
  // Subscribes to a mailbox
6
6
  module.exports = async (connection, path) => {
@@ -17,12 +17,7 @@ module.exports = async (connection, path) => {
17
17
  response.next();
18
18
  return true;
19
19
  } catch (err) {
20
- let errorCode = getStatusCode(err.response);
21
- if (errorCode) {
22
- err.serverResponseCode = errorCode;
23
- }
24
- err.response = await getErrorText(err.response);
25
-
20
+ await enhanceCommandError(err);
26
21
  connection.log.warn({ err, cid: connection.id });
27
22
  return false;
28
23
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { encodePath, normalizePath, getStatusCode, getErrorText } = require('../tools.js');
3
+ const { encodePath, normalizePath, enhanceCommandError } = require('../tools.js');
4
4
 
5
5
  // Unsubscribes from a mailbox
6
6
  module.exports = async (connection, path) => {
@@ -17,13 +17,7 @@ module.exports = async (connection, path) => {
17
17
  response.next();
18
18
  return true;
19
19
  } catch (err) {
20
- let errorCode = getStatusCode(err.response);
21
- if (errorCode) {
22
- err.serverResponseCode = errorCode;
23
- }
24
-
25
- err.response = await getErrorText(err.response);
26
-
20
+ await enhanceCommandError(err);
27
21
  connection.log.warn({ err, cid: connection.id });
28
22
  return false;
29
23
  }
@@ -252,6 +252,21 @@ class ImapStream extends Transform {
252
252
  _flush(next) {
253
253
  next();
254
254
  }
255
+
256
+ _destroy(err, callback) {
257
+ this.inputBuffer = [];
258
+ this.lineBuffer = [];
259
+ this.literalBuffer = [];
260
+ this.literals = [];
261
+ // Clear inputQueue and call any pending callbacks
262
+ while (this.inputQueue.length) {
263
+ const item = this.inputQueue.shift();
264
+ if (typeof item.next === 'function') {
265
+ item.next();
266
+ }
267
+ }
268
+ callback(err);
269
+ }
255
270
  }
256
271
 
257
272
  module.exports.ImapStream = ImapStream;
package/lib/imap-flow.js CHANGED
@@ -372,7 +372,8 @@ class ImapFlow extends EventEmitter {
372
372
 
373
373
  this.disableBinary = !!this.options.disableBinary;
374
374
 
375
- this.streamer.on('error', err => {
375
+ // Named error handler for proper cleanup
376
+ this._streamerErrorHandler = err => {
376
377
  if (['Z_BUF_ERROR', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EHOSTUNREACH'].includes(err.code)) {
377
378
  // just close the connection, usually nothing but noise
378
379
  this.closeAfter();
@@ -381,7 +382,8 @@ class ImapFlow extends EventEmitter {
381
382
 
382
383
  this.log.error({ err, cid: this.id });
383
384
  this.emitError(err);
384
- });
385
+ };
386
+ this.streamer.on('error', this._streamerErrorHandler);
385
387
 
386
388
  // Has the `connect` method already been called
387
389
  this._connectCalled = false;
@@ -772,6 +774,12 @@ class ImapFlow extends EventEmitter {
772
774
  // Clear any existing handlers first to prevent duplicates
773
775
  this.clearSocketHandlers();
774
776
 
777
+ // Remove temporary connection error handler if present
778
+ if (this._connectErrorHandler && this.socket) {
779
+ this.socket.removeListener('error', this._connectErrorHandler);
780
+ this._connectErrorHandler = null;
781
+ }
782
+
775
783
  this._socketError =
776
784
  this._socketError ||
777
785
  (err => {
@@ -828,6 +836,12 @@ class ImapFlow extends EventEmitter {
828
836
  return;
829
837
  }
830
838
 
839
+ // Remove temporary connection error handler if still present
840
+ if (this._connectErrorHandler) {
841
+ this.socket.removeListener('error', this._connectErrorHandler);
842
+ this._connectErrorHandler = null;
843
+ }
844
+
831
845
  if (this._socketError) {
832
846
  this.socket.removeListener('error', this._socketError);
833
847
  this.socket.removeListener('tlsClientError', this._socketError);
@@ -1043,7 +1057,8 @@ class ImapFlow extends EventEmitter {
1043
1057
  );
1044
1058
  this.clearSocketHandlers();
1045
1059
 
1046
- socketPlain.once('error', err => {
1060
+ // Store error handler for cleanup after successful upgrade
1061
+ const socketPlainErrorHandler = err => {
1047
1062
  clearTimeout(this.connectTimeout);
1048
1063
  clearTimeout(this.upgradeTimeout);
1049
1064
  if (!this.upgrading) {
@@ -1054,7 +1069,8 @@ class ImapFlow extends EventEmitter {
1054
1069
  this.upgrading = false;
1055
1070
  err.tlsFailed = true;
1056
1071
  reject(err);
1057
- });
1072
+ };
1073
+ socketPlain.once('error', socketPlainErrorHandler);
1058
1074
 
1059
1075
  this.upgradeTimeout = setTimeout(() => {
1060
1076
  if (!this.upgrading) {
@@ -1093,6 +1109,9 @@ class ImapFlow extends EventEmitter {
1093
1109
  });
1094
1110
  }
1095
1111
 
1112
+ // Clean up the plain socket error handler after successful upgrade
1113
+ socketPlain.removeListener('error', socketPlainErrorHandler);
1114
+
1096
1115
  return resolve(true);
1097
1116
  } catch (ex) {
1098
1117
  this.emitError(ex);
@@ -1175,13 +1194,7 @@ class ImapFlow extends EventEmitter {
1175
1194
  throw new AuthenticationFailure('No matching authentication method');
1176
1195
  }
1177
1196
 
1178
- async initialOK(message) {
1179
- this.greeting = (message.attributes || [])
1180
- .filter(entry => entry.type === 'TEXT')
1181
- .map(entry => entry.value)
1182
- .filter(entry => entry)
1183
- .join('');
1184
-
1197
+ beginSession(onUnhandledError) {
1185
1198
  clearTimeout(this.greetingTimeout);
1186
1199
  this.untaggedHandlers.OK = null;
1187
1200
  this.untaggedHandlers.PREAUTH = null;
@@ -1211,45 +1224,30 @@ class ImapFlow extends EventEmitter {
1211
1224
  return reject(err);
1212
1225
  }
1213
1226
 
1214
- // ALWAYS emit the error so users can handle it
1215
- this.emitError(err);
1227
+ onUnhandledError(err);
1216
1228
  });
1217
1229
  }
1218
1230
 
1219
- async initialPREAUTH() {
1220
- clearTimeout(this.greetingTimeout);
1221
- this.untaggedHandlers.OK = null;
1222
- this.untaggedHandlers.PREAUTH = null;
1231
+ async initialOK(message) {
1232
+ this.greeting = (message.attributes || [])
1233
+ .filter(entry => entry.type === 'TEXT')
1234
+ .map(entry => entry.value)
1235
+ .filter(entry => entry)
1236
+ .join('');
1223
1237
 
1238
+ // ALWAYS emit the error so users can handle it
1239
+ this.beginSession(err => this.emitError(err));
1240
+ }
1241
+
1242
+ async initialPREAUTH() {
1224
1243
  if (this.isClosed) {
1225
1244
  return;
1226
1245
  }
1227
-
1228
1246
  this.state = this.states.AUTHENTICATED;
1229
-
1230
- // get out of current parsing "thread", so do not await for startSession
1231
- this.startSession()
1232
- .then(() => {
1233
- if (typeof this.initialResolve === 'function') {
1234
- let resolve = this.initialResolve;
1235
- this.initialResolve = false;
1236
- this.initialReject = false;
1237
- return resolve();
1238
- }
1239
- })
1240
- .catch(err => {
1241
- this.log.error({ err, cid: this.id });
1242
-
1243
- if (typeof this.initialReject === 'function') {
1244
- clearTimeout(this.greetingTimeout);
1245
- let reject = this.initialReject;
1246
- this.initialResolve = false;
1247
- this.initialReject = false;
1248
- return reject(err);
1249
- }
1250
-
1251
- this.closeAfter();
1252
- });
1247
+ this.beginSession(err => {
1248
+ this.log.error({ err, cid: this.id });
1249
+ this.closeAfter();
1250
+ });
1253
1251
  }
1254
1252
 
1255
1253
  async serverBye(parsed) {
@@ -1267,9 +1265,9 @@ class ImapFlow extends EventEmitter {
1267
1265
  this.state = this.states.LOGOUT;
1268
1266
  }
1269
1267
 
1270
- async sectionCapability(section) {
1271
- this.rawCapabilities = section;
1272
- this.capabilities = updateCapabilities(section);
1268
+ updateCapabilitiesFromRaw(rawCapabilities) {
1269
+ this.rawCapabilities = rawCapabilities;
1270
+ this.capabilities = updateCapabilities(rawCapabilities);
1273
1271
 
1274
1272
  if (this.capabilities) {
1275
1273
  for (let [capa] of this.capabilities) {
@@ -1284,21 +1282,12 @@ class ImapFlow extends EventEmitter {
1284
1282
  }
1285
1283
  }
1286
1284
 
1287
- async untaggedCapability(untagged) {
1288
- this.rawCapabilities = untagged.attributes;
1289
- this.capabilities = updateCapabilities(untagged.attributes);
1290
-
1291
- if (this.capabilities) {
1292
- for (let [capa] of this.capabilities) {
1293
- if (/^AUTH=/i.test(capa) && !this.authCapabilities.has(capa.toUpperCase())) {
1294
- this.authCapabilities.set(capa.toUpperCase(), false);
1295
- }
1296
- }
1297
- }
1285
+ async sectionCapability(section) {
1286
+ this.updateCapabilitiesFromRaw(section);
1287
+ }
1298
1288
 
1299
- if (this.expectCapabilityUpdate) {
1300
- this.expectCapabilityUpdate = false;
1301
- }
1289
+ async untaggedCapability(untagged) {
1290
+ this.updateCapabilitiesFromRaw(untagged.attributes);
1302
1291
  }
1303
1292
 
1304
1293
  async untaggedExists(untagged) {
@@ -1637,13 +1626,15 @@ class ImapFlow extends EventEmitter {
1637
1626
 
1638
1627
  this.writeSocket = this.socket;
1639
1628
 
1640
- this.socket.on('error', err => {
1629
+ // Store connection error handler for cleanup
1630
+ this._connectErrorHandler = err => {
1641
1631
  clearTimeout(this.connectTimeout);
1642
1632
  clearTimeout(this.greetingTimeout);
1643
1633
  this.closeAfter();
1644
1634
  this.log.error({ err, cid: this.id });
1645
1635
  reject(err);
1646
- });
1636
+ };
1637
+ this.socket.on('error', this._connectErrorHandler);
1647
1638
  });
1648
1639
  }
1649
1640
 
@@ -1791,11 +1782,13 @@ class ImapFlow extends EventEmitter {
1791
1782
  // cleanup streamer
1792
1783
  if (this.streamer) {
1793
1784
  try {
1794
- // remove our listeners
1785
+ // remove our listeners explicitly by reference
1795
1786
  if (this.socketReadable) {
1796
1787
  this.streamer.removeListener('readable', this.socketReadable);
1797
1788
  }
1798
- this.streamer.removeAllListeners('error');
1789
+ if (this._streamerErrorHandler) {
1790
+ this.streamer.removeListener('error', this._streamerErrorHandler);
1791
+ }
1799
1792
  if (!this.streamer.destroyed) {
1800
1793
  this.streamer.destroy();
1801
1794
  }
@@ -1841,6 +1834,18 @@ class ImapFlow extends EventEmitter {
1841
1834
  }
1842
1835
  }
1843
1836
 
1837
+ // Explicit nullification to help garbage collection
1838
+ this.socket = null;
1839
+ this.writeSocket = null;
1840
+ this._inflate = null;
1841
+ this._deflate = null;
1842
+ this._streamerErrorHandler = null;
1843
+ this._connectErrorHandler = null;
1844
+ this._socketError = null;
1845
+ this._socketClose = null;
1846
+ this._socketEnd = null;
1847
+ this._socketTimeout = null;
1848
+
1844
1849
  this.log.trace({ msg: 'Connection closed', cid: this.id });
1845
1850
  this.emit('close');
1846
1851
  } catch (ex) {
package/lib/jp-decoder.js CHANGED
@@ -41,6 +41,12 @@ class JPDecoder extends Transform {
41
41
 
42
42
  done();
43
43
  }
44
+
45
+ _destroy(err, callback) {
46
+ this.chunks = [];
47
+ this.chunklen = 0;
48
+ callback(err);
49
+ }
44
50
  }
45
51
 
46
52
  module.exports.JPDecoder = JPDecoder;