@bobfrankston/iflow 1.0.44 → 1.0.46

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.
@@ -472,6 +472,14 @@ export class NativeImapClient {
472
472
  this.buffer = this.buffer.substring(this.pendingCommand.literalBytes);
473
473
  this.pendingCommand.literalBuffer = (this.pendingCommand.literalBuffer || "") + literal;
474
474
  this.pendingCommand.literalBytes = undefined;
475
+ // Store the literal data by its BODY key for parseFetchResponses
476
+ if (this.pendingCommand.currentLiteralKey) {
477
+ if (!this.pendingCommand.literals)
478
+ this.pendingCommand.literals = new Map();
479
+ this.pendingCommand.literals.set(this.pendingCommand.currentLiteralKey, literal);
480
+ this.pendingCommand.currentLiteralKey = undefined;
481
+ this.pendingCommand.currentLiteralSize = undefined;
482
+ }
475
483
  if (this.verbose)
476
484
  console.log(` [imap] literal consumed, ${literal.length} bytes, buffer remaining: ${this.buffer.length}`);
477
485
  // After consuming literal, check if the NEXT part has another literal
@@ -503,8 +511,14 @@ export class NativeImapClient {
503
511
  this.pendingCommand.literalBuffer = linePrefix;
504
512
  }
505
513
  this.pendingCommand.literalBytes = size;
514
+ // Extract the BODY section key from the prefix (e.g. BODY[], BODY[HEADER], BODY.PEEK[])
515
+ const keyMatch = linePrefix.match(/BODY(?:\.PEEK)?\[([^\]]*)\]\s*$/i);
516
+ if (keyMatch) {
517
+ this.pendingCommand.currentLiteralKey = `BODY[${keyMatch[1]}]`;
518
+ this.pendingCommand.currentLiteralSize = size;
519
+ }
506
520
  if (this.verbose)
507
- console.log(` [imap] literal announced: ${size} bytes, prefix: ${linePrefix.length} chars`);
521
+ console.log(` [imap] literal announced: ${size} bytes, prefix: ${linePrefix.length} chars, key: ${this.pendingCommand.currentLiteralKey || "none"}`);
508
522
  continue;
509
523
  }
510
524
  // If we have buffered literal data, prepend it to this line (the closing part after the literal)
@@ -514,6 +528,11 @@ export class NativeImapClient {
514
528
  this.pendingCommand.literalBuffer = undefined;
515
529
  }
516
530
  const resp = proto.parseResponseLine(fullLine);
531
+ // Attach accumulated literals to the response and reset for next response
532
+ if (this.pendingCommand?.literals?.size) {
533
+ resp.literals = this.pendingCommand.literals;
534
+ this.pendingCommand.literals = undefined;
535
+ }
517
536
  if (this.verbose) {
518
537
  const display = resp.raw.length > 200 ? resp.raw.substring(0, 200) + `... (${resp.raw.length} bytes)` : resp.raw;
519
538
  console.log(` [imap] < [tag=${resp.tag} type=${resp.type}] ${display}`);
@@ -622,9 +641,15 @@ export class NativeImapClient {
622
641
  msg.replyTo = env.replyTo;
623
642
  msg.inReplyTo = env.inReplyTo;
624
643
  }
625
- // Source body is in literal data (handled by literal buffering in processBuffer)
626
- // For now, simplified — the literal handling needs more work for production
627
- // TODO: improve literal parsing for BODY[] and BODY[HEADER]
644
+ // Extract body source and headers from literals tracked by processBuffer
645
+ if (r.literals) {
646
+ const source = r.literals.get("BODY[]");
647
+ if (source)
648
+ msg.source = source;
649
+ const headers = r.literals.get("BODY[HEADER]");
650
+ if (headers)
651
+ msg.headers = headers;
652
+ }
628
653
  messages.push(msg);
629
654
  }
630
655
  return messages;
@@ -13,6 +13,8 @@ export interface ImapResponse {
13
13
  text: string;
14
14
  /** Raw line */
15
15
  raw: string;
16
+ /** Literal data keyed by BODY section name (e.g. "BODY[]", "BODY[HEADER]") */
17
+ literals?: Map<string, string>;
16
18
  }
17
19
  /** Parsed FETCH response data */
18
20
  export interface FetchData {
@@ -247,12 +247,26 @@ function decodeImapString(s) {
247
247
  return "";
248
248
  return s.replace(/=\?([^?]+)\?([BQ])\?([^?]+)\?=/gi, (_match, charset, encoding, text) => {
249
249
  try {
250
+ const cs = charset.toLowerCase().replace(/^utf8$/, "utf-8");
250
251
  if (encoding.toUpperCase() === "B") {
251
- return Buffer.from(text, "base64").toString("utf-8");
252
+ return Buffer.from(text, "base64").toString(cs);
252
253
  }
253
254
  else {
254
- // Quoted-printable
255
- return text.replace(/=([0-9A-F]{2})/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16))).replace(/_/g, " ");
255
+ // Quoted-printable: collect bytes then decode with charset
256
+ const decoded = text.replace(/_/g, " ");
257
+ const bytes = [];
258
+ let i = 0;
259
+ while (i < decoded.length) {
260
+ if (decoded[i] === "=" && i + 2 < decoded.length) {
261
+ bytes.push(parseInt(decoded.substring(i + 1, i + 3), 16));
262
+ i += 3;
263
+ }
264
+ else {
265
+ bytes.push(decoded.charCodeAt(i));
266
+ i++;
267
+ }
268
+ }
269
+ return Buffer.from(bytes).toString(cs);
256
270
  }
257
271
  }
258
272
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/iflow",
3
- "version": "1.0.44",
3
+ "version": "1.0.46",
4
4
  "description": "IMAP client wrapper library",
5
5
  "main": "index.js",
6
6
  "types": "index.ts",