Skip to content

Commit

Permalink
Merge pull request #19 from t0mmar/issue/bodystructure_parsing
Browse files Browse the repository at this point in the history
Fix issues #16, #18
  • Loading branch information
michaelspiss committed Aug 9, 2019
2 parents 2fec181 + ae44e2c commit 825508f
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 4 deletions.
49 changes: 45 additions & 4 deletions lib/src/imap_folder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -415,11 +415,16 @@ class ImapFolder extends _ImapCommandable {
throw new SyntaxErrorException("Expected number, got $word");
}
} else if (word.type == ImapWordType.parenOpen) {
value = new List<String>();
word = await buffer.readWord();
while (word.type != ImapWordType.parenClose) {
value.add(word.value);
if (extCount == 1) {
// "DISPOSITION"
value = await _processDispositionSubfields(buffer);
} else {
value = new List<String>();
word = await buffer.readWord();
while (word.type != ImapWordType.parenClose) {
value.add(word.value);
word = await buffer.readWord();
}
}
} else {
throw new SyntaxErrorException(
Expand Down Expand Up @@ -503,6 +508,42 @@ class ImapFolder extends _ImapCommandable {
};
}

static Future<Map<String, dynamic>> _processDispositionSubfields(
ImapBuffer buffer) async {
Map<String, dynamic> subfieldMap = new Map();

// expects prenOpen to be consumed already
ImapWord word = await buffer.readWord();
while (word.type != ImapWordType.parenClose) {
if (word.type != ImapWordType.nil) {
if (word.type != ImapWordType.string) {
throw new InvalidFormatException(
"Expected ), NIL or string, but got ${word.type}");
}
String key = word.value;
word = await buffer.readWord();
if (word.type != ImapWordType.nil) {
Map<String, String> values = new Map();
if (word.type != ImapWordType.parenOpen) {
throw new InvalidFormatException(
"Expected (, but got ${word.type}");
}
word = await buffer.readWord();
while (word.type != ImapWordType.parenClose) {
values[word.value] =
(await buffer.readWord(expected: ImapWordType.string)).value;
word = await buffer.readWord();
}
subfieldMap.addAll({key: values});
}
}

word = await buffer.readWord();
}

return subfieldMap;
}

/*
Other helpers
*/
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ dependencies:

dev_dependencies:
test: ^1.3.0
mockito: ^4.1.0
121 changes: 121 additions & 0 deletions test/imap_bodystructure_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import 'dart:async';
import 'dart:io';

import 'package:imap_client/imap_client.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';

class FakeSocket extends Fake implements Socket {
StreamController<List<int>> input;
String nextResponse;

FakeSocket(this.input);

@override
StreamSubscription<List<int>> listen(void onData(List<int> event),
{Function onError, void onDone(), bool cancelOnError}) {
return input.stream.listen(onData);
}

@override
void write(Object object) async {
if (object is String) {
if (object.startsWith("A2")) {
input.add("A2 OK\r\n".codeUnits);
} else if (object.contains("FETCH")) {
input.add("$nextResponse\r\n".codeUnits);
}
}
}
}

void main() {
StreamController<List<int>> server = new StreamController.broadcast();
FakeSocket socket = new FakeSocket(server);
ImapEngine engine;
ImapFolder _folder;

setUp(() {
server.stream.drain();
engine = new ImapEngine(socket);
_folder = new ImapFolder(engine, "TestFolder");
server.add("* OK [CAPABILITY Imap4rev1]\r\nA1 OK\r\n".codeUnits);
});

/*
Test cases are based on the following examples:
http://sgerwk.altervista.org/imapbodystructure.html
These just check to make sure no exceptions are thrown, but could be expanded to verify expected _result
*/

test('single-part email', () async {
socket.nextResponse =
"""* 1 FETCH (BODYSTRUCTURE("TEXT" "PLAIN" ("CHARSET" "iso-8859-1") NIL NIL "QUOTED-PRINTABLE" 1315 42 NIL NIL NIL NIL))\r\nA1 OK\r\n""";
await _folder.fetch([], messageIds: [1]);
});

test('text + html', () async {
socket.nextResponse =
"""* 1 FETCH (BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "iso-8859-1") NIL NIL "QUOTED-PRINTABLE" 2234 63 NIL NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "iso-8859-1") NIL NIL "QUOTED-PRINTABLE" 2987 52 NIL NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "d3438gr7324") NIL NIL NIL))\r\nA1 OK\r\n""";
await _folder.fetch([], messageIds: [1]);
});

test('mail with images', () async {
socket.nextResponse =
"""* 1 FETCH (BODYSTRUCTURE (("TEXT" "HTML" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 119 2 NIL ("INLINE" NIL) NIL)("IMAGE" "JPEG" ("NAME" "4356415.jpg") "<0__=rhksjt>" NIL "BASE64" 143804 NIL ("INLINE" ("FILENAME" "4356415.jpg")) NIL) "RELATED" ("BOUNDARY" "0__=5tgd3d") ("INLINE" NIL) NIL))\r\nA1 OK\r\n""";
await _folder.fetch([], messageIds: [1]);
});

test('text + html with images', () async {
socket.nextResponse =
"""* 1 FETCH (BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "ISO-8859-1" "FORMAT" "flowed") NIL NIL "QUOTED-PRINTABLE" 2815 73 NIL NIL NIL NIL)(("TEXT" "HTML" ("CHARSET" "ISO-8859-1") NIL NIL "QUOTED-PRINTABLE" 4171 66 NIL NIL NIL NIL)("IMAGE" "JPEG" ("NAME" "image.jpg") "<3245dsf7435>" NIL "BASE64" 189906 NIL NIL NIL NIL)("IMAGE" "GIF" ("NAME" "other.gif") "<32f6324f>" NIL "BASE64" 1090 NIL NIL NIL NIL) "RELATED" ("BOUNDARY" "--=sdgqgt") NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "--=u5sfrj") NIL NIL NIL))\r\nA1 OK\r\n""";
await _folder.fetch([], messageIds: [1]);
});

test('mail with images', () async {
socket.nextResponse =
"""* 1 FETCH (BODYSTRUCTURE ((("TEXT" "PLAIN" ("CHARSET" "ISO-8859-1") NIL NIL "QUOTED-PRINTABLE" 471 28 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "ISO-8859-1") NIL NIL "QUOTED-PRINTABLE" 1417 36 NIL ("INLINE" NIL) NIL) "ALTERNATIVE" ("BOUNDARY" "1__=hqjksdm") NIL NIL)("IMAGE" "GIF" ("NAME" "image.gif") "<1__=cxdf2f>" NIL "BASE64" 50294 NIL ("INLINE" ("FILENAME" "image.gif")) NIL) "RELATED" ("BOUNDARY" "0__=hqjksdm") NIL NIL))\r\nA1 OK\r\n""";
await _folder.fetch([], messageIds: [1]);
});

test('mail with attachment', () async {
socket.nextResponse =
"""* 1 FETCH (BODYSTRUCTURE (("TEXT" "HTML" ("CHARSET" "ISO-8859-1") NIL NIL "QUOTED-PRINTABLE" 4692 69 NIL NIL NIL NIL)("APPLICATION" "PDF" ("NAME" "pages.pdf") NIL NIL "BASE64" 38838 NIL ("attachment" ("FILENAME" "pages.pdf")) NIL NIL) "MIXED" ("BOUNDARY" "----=6fgshr") NIL NIL NIL))\r\nA1 OK\r\n""";
await _folder.fetch([], messageIds: [1]);
});

test('alternative and attachment', () async {
socket.nextResponse =
"""* 1 FETCH (BODYSTRUCTURE ((("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 403 6 NIL NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "QUOTED-PRINTABLE" 421 6 NIL NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "----=fghgf3") NIL NIL NIL)("APPLICATION" "MSWORD" ("NAME" "letter.doc") NIL NIL "BASE64" 110000 NIL ("attachment" ("FILENAME" "letter.doc" "SIZE" "80384")) NIL NIL) "MIXED" ("BOUNDARY" "----=y34fgl") NIL NIL NIL))\r\nA1 OK\r\n""";
await _folder.fetch([], messageIds: [1]);
});

test('all together', () async {
socket.nextResponse =
"""* 1 FETCH (BODYSTRUCTURE (((("TEXT" "PLAIN" ("CHARSET" "ISO-8859-1") NIL NIL "QUOTED-PRINTABLE" 833 30 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "ISO-8859-1") NIL NIL "QUOTED-PRINTABLE" 3412 62 NIL ("INLINE" NIL) NIL) "ALTERNATIVE" ("BOUNDARY" "2__=fgrths") NIL NIL)("IMAGE" "GIF" ("NAME" "485039.gif") "<2__=lgkfjr>" NIL "BASE64" 64 NIL ("INLINE" ("FILENAME" "485039.gif")) NIL) "RELATED" ("BOUNDARY" "1__=fgrths") NIL NIL)("APPLICATION" "PDF" ("NAME" "title.pdf") "<1__=lgkfjr>" NIL "BASE64" 333980 NIL ("ATTACHMENT" ("FILENAME" "title.pdf")) NIL) "MIXED" ("BOUNDARY" "0__=fgrths") NIL NIL))\r\nA1 OK\r\n""";
await _folder.fetch([], messageIds: [1]);
});

test('single-element lists', () async {
socket.nextResponse =
"""* 1 FETCH (BODYSTRUCTURE (("TEXT" "HTML" NIL NIL NIL "7BIT" 151 0 NIL NIL NIL) "MIXED" ("BOUNDARY" "----=rfsewr") NIL NIL))\r\nA1 OK\r\n""";
await _folder.fetch([], messageIds: [1]);
});

//--- other test variations

test('text + html with "inline" images', () async {
socket.nextResponse =
"""* 1 FETCH (BODYSTRUCTURE (("text" "plain" ("charset" "utf-8") NIL NIL "7bit" 160 10 NIL NIL NIL NIL)(("text" "html" ("charset" "utf-8") NIL NIL "7bit" 452 15 NIL NIL NIL NIL)("image" "png" ("name" "kmdhkbcgedflagom.png") "<part1.949092FE.5A7457AA@librem.one>" NIL "base64" 29594 NIL ("inline" ("filename" "kmdhkbcgedflagom.png")) NIL NIL) "related" ("boundary" "------------4F31590A57C94ECEEA4EE609") NIL NIL NIL) "alternative" ("boundary" "------------E4A51DD138E4E2F27347C5BC") NIL ("en-US") NIL))\r\nA1 OK\r\n""";
await _folder.fetch([], messageIds: [1]);
});

test('"inline" pgp signature', () async {
socket.nextResponse =
"""* 1 FETCH (BODYSTRUCTURE (("application" "pgp-encrypted" NIL NIL NIL "7bit" 12 NIL NIL NIL NIL)("application" "octet-stream" ("name" "encrypted.asc") NIL NIL "7bit" 933 NIL ("inline" ("filename" "encrypted.asc")) NIL NIL) "encrypted" ("boundary" "5ceef4c9_74b0dc51_1a6" "protocol" "application/pgp-encrypted") NIL NIL NIL))\r\nA1 OK\r\n""";
await _folder.fetch([], messageIds: [1]);
});
}

0 comments on commit 825508f

Please sign in to comment.