From 46b59ee6181e036c0a30d12513338b86bf8763b9 Mon Sep 17 00:00:00 2001 From: Robert Virkus Date: Sat, 10 Apr 2021 10:08:11 +0200 Subject: [PATCH] handle raw data in parameter values --- lib/src/imap/fetch_parser.dart | 23 ++++++--- lib/src/imap/imap_response.dart | 6 ++- test/src/imap/fetch_parser_test.dart | 71 ++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 9 deletions(-) diff --git a/lib/src/imap/fetch_parser.dart b/lib/src/imap/fetch_parser.dart index ec9a58b2..66af28f6 100644 --- a/lib/src/imap/fetch_parser.dart +++ b/lib/src/imap/fetch_parser.dart @@ -223,6 +223,7 @@ class FetchParser extends ResponseParser { /// * https://tools.ietf.org/html/rfc3501#section-7.4.2 /// * http://hea-www.cfa.harvard.edu/~fine/opinions/IMAPsucks.html void _parseBodyRecursive(BodyPart body, ImapValue bodyValue) { + // print('_parseBodyRecursive from $bodyValue'); var isMultipartSubtypeSet = false; var multipartChildIndex = -1; final children = bodyValue.children!; @@ -269,8 +270,8 @@ class FetchParser extends ResponseParser { child.children!.length > 1) { final parameters = child.children!; for (var i = 0; i < parameters.length; i += 2) { - body.contentType! - .setParameter(parameters[i].value!, parameters[i + 1].value); + body.contentType!.setParameter( + parameters[i].value!, parameters[i + 1].valueOrDataText!); } } } @@ -289,9 +290,13 @@ class FetchParser extends ResponseParser { final contentTypeParameters = structs[2].children; if (contentTypeParameters != null && contentTypeParameters.length > 1) { for (var i = 0; i < contentTypeParameters.length; i += 2) { - final name = contentTypeParameters[i].value!; - final value = contentTypeParameters[i + 1].value; - part.contentType!.setParameter(name, value); + final name = contentTypeParameters[i].value; + + final value = contentTypeParameters[i + 1].valueOrDataText; + // print('content-type: $name=$value'); + if (name != null && value != null) { + part.contentType!.setParameter(name, value); + } } } var startIndex = 7; @@ -325,8 +330,12 @@ class FetchParser extends ResponseParser { final parameters = parts[1].children; if (parameters != null && parameters.length > 1) { for (var i = 0; i < parameters.length; i += 2) { - contentDisposition.setParameter( - parameters[i].value!, parameters[i + 1].value); + final name = parameters[i].value; + final value = parameters[i + 1].valueOrDataText; + if (name != null && value != null) { + // print('content-disposition: $name=$value'); + contentDisposition.setParameter(name, value); + } } } part.contentDisposition = contentDisposition; diff --git a/lib/src/imap/imap_response.dart b/lib/src/imap/imap_response.dart index 5d6cef47..7800c128 100644 --- a/lib/src/imap/imap_response.dart +++ b/lib/src/imap/imap_response.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:typed_data'; import 'package:enough_mail/src/util/ascii_runes.dart'; @@ -194,7 +195,7 @@ class ImapValue { bool get hasChildren => children?.isNotEmpty ?? false; String? get valueOrDataText => - value ?? (data == null ? null : String.fromCharCodes(data!)); + value ?? (data == null ? null : utf8.decode(data!, allowMalformed: true)); void addChild(ImapValue child) { children ??= []; @@ -204,6 +205,7 @@ class ImapValue { @override String toString() { - return (value ?? '') + (children != null ? children.toString() : ''); + return (value ?? (data != null ? '<${data!.length} bytes>' : '')) + + (children != null ? children.toString() : ''); } } diff --git a/test/src/imap/fetch_parser_test.dart b/test/src/imap/fetch_parser_test.dart index 8b971179..01d0f739 100644 --- a/test/src/imap/fetch_parser_test.dart +++ b/test/src/imap/fetch_parser_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; +import 'dart:io'; import 'dart:typed_data'; import 'package:enough_convert/enough_convert.dart'; @@ -863,6 +865,75 @@ void main() { expect(body.parts![0].fetchId, '1'); }); + test('BODYSTRUCTURE 14 - with raw data parameters', () { + final contentType = ContentTypeHeader('application/pdf'); + contentType.setParameter('name', 'FileName.pdf'); + expect(contentType.parameters['name'], 'FileName.pdf'); + final contentDisposition = ContentDispositionHeader('attachment'); + contentDisposition.setParameter('filename', 'FileName.pdf'); + expect(contentDisposition.filename, 'FileName.pdf'); + var responseTexts = [ + '* 63644 FETCH (UID 351739 BODYSTRUCTURE (("TEXT" "html" ("charset" "utf-8") NIL NIL "BASE64" 5234 68 NIL NIL NIL NIL)("APPLICATION" "pdf" ("name" "Testpflicht an Schulen_09_04_21.pdf") NIL NIL "BASE64" 638510 NIL ("attachment" ("filename" "Testpflicht an Schulen_09_04_21.pdf" "size" "466602")) NIL NIL)("APPLICATION" "pdf" ("name" {42}', + 'Schnelltest Einverständniserklärung3.pdf', + ') NIL NIL "7BIT" 239068 NIL ("attachment" ("filename" {42}', + 'Schnelltest Einverständniserklärung3.pdf', + '"size" "174701")) NIL NIL) "mixed" ("boundary" "--_com.android.email_1204848368992460") NIL NIL NIL))' + ]; + var details = ImapResponse(); + var lastLineEndedInData = false; + for (var text in responseTexts) { + if (lastLineEndedInData) { + final rawData = utf8.encode(text) as Uint8List; + details.add(ImapResponseLine.raw(rawData)); + lastLineEndedInData = false; + } else { + details.add(ImapResponseLine(text)); + lastLineEndedInData = text.endsWith('}'); + } + } + var parser = FetchParser(false); + var response = Response()..status = ResponseStatus.OK; + var processed = parser.parseUntagged(details, response); + expect(processed, true); + var messages = parser.parse(details, response)!.messages; + expect(messages, isNotNull); + expect(messages.length, 1); + var body = messages[0].body; + //print('parsed body part: \n$body'); + expect(body, isNotNull); + expect(body!.contentType, isNotNull); + expect(body.contentType!.mediaType, isNotNull); + expect(body.contentType!.mediaType.top, MediaToptype.multipart); + expect(body.contentType!.mediaType.sub, MediaSubtype.multipartMixed); + expect(body.contentType!.boundary, '--_com.android.email_1204848368992460'); + expect(body.parts?.length, 3); + expect(body.parts![0].contentType!.mediaType.top, MediaToptype.text); + expect(body.parts![0].contentType!.mediaType.sub, MediaSubtype.textHtml); + expect(body.parts![0].encoding, 'base64'); + expect(body.parts![0].size, 5234); + expect( + body.parts![1].contentType!.mediaType.sub, MediaSubtype.applicationPdf); + expect(body.parts![1].contentType!.parameters['name'], + 'Testpflicht an Schulen_09_04_21.pdf'); + expect(body.parts![1].contentDisposition, isNotNull); + expect(body.parts![1].contentDisposition!.disposition, + ContentDisposition.attachment); + expect(body.parts![1].contentDisposition!.filename, + 'Testpflicht an Schulen_09_04_21.pdf'); + expect(body.parts![1].contentDisposition!.size, 466602); + + expect( + body.parts![2].contentType!.mediaType.sub, MediaSubtype.applicationPdf); + expect(body.parts![2].contentType!.parameters['name'], + 'Schnelltest Einverständniserklärung3.pdf'); + expect(body.parts![2].contentDisposition, isNotNull); + expect(body.parts![2].contentDisposition!.disposition, + ContentDisposition.attachment); + expect(body.parts![2].contentDisposition!.filename, + 'Schnelltest Einverständniserklärung3.pdf'); + expect(body.parts![2].contentDisposition!.size, 174701); + }); + test('MODSEQ', () { var responseText = '* 50 FETCH (MODSEQ (12111230047))'; var details = ImapResponse()..add(ImapResponseLine(responseText));