From e644ca71fdf58e3f3fd99fb130a4513639019f81 Mon Sep 17 00:00:00 2001 From: michaelspiss Date: Fri, 9 Aug 2019 14:42:22 +0200 Subject: [PATCH 1/5] Change package description More accurate description, this package does not only provide an interface --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2e3755b..1ed7827 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: imap_client -description: An interface to get emails via the imap protocol (version 4rev1) +description: IMAP (version 4rev1) implementation for dart version: 0.2.5 homepage: https://github.com/michaelspiss/imap_client author: Michael Spiss From fa2a540592b1acd67e85fbe670d81c7b3ff64d9c Mon Sep 17 00:00:00 2001 From: michaelspiss Date: Fri, 9 Aug 2019 14:50:40 +0200 Subject: [PATCH 2/5] Fix code style: Use curly brackets for control flow structures --- lib/src/imap_buffer.dart | 10 ++++++---- lib/src/imap_engine.dart | 6 ++++-- lib/src/imap_folder.dart | 6 +++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/src/imap_buffer.dart b/lib/src/imap_buffer.dart index e3b7ca4..7095cd2 100644 --- a/lib/src/imap_buffer.dart +++ b/lib/src/imap_buffer.dart @@ -79,10 +79,12 @@ class ImapBuffer { {autoReleaseBuffer = true, ImapWordType expected}) async { int charAtPosition = await skipWhitespaces(); ImapWord word; - if (_specialChars.containsKey(charAtPosition)) - word = ImapWord(_specialChars[charAtPosition], - String.fromCharCode(await _getCharCode(proceed: true))); - else if (charAtPosition == 34 /* " */) { + if (_specialChars.containsKey(charAtPosition)) { + word = ImapWord( + _specialChars[charAtPosition], + String.fromCharCode(await _getCharCode(proceed: true)), + ); + } else if (charAtPosition == 34 /* " */) { word = await readQuotedString(autoReleaseBuffer: false); } else if (charAtPosition == 123 /* { */) { word = await readLiteral(autoReleaseBuffer: false); diff --git a/lib/src/imap_engine.dart b/lib/src/imap_engine.dart index 9ab0dd1..247edf9 100644 --- a/lib/src/imap_engine.dart +++ b/lib/src/imap_engine.dart @@ -131,9 +131,11 @@ class ImapEngine { _queue.addFirst(_currentInstruction); _queue.addFirst(select); ImapTaggedResponse response = await executeCommand(select); - if (response == ImapTaggedResponse.no) + if (response == ImapTaggedResponse.no) { _currentFolder = null; - else if (response == ImapTaggedResponse.bad) _currentFolder = oldFolder; + } else if (response == ImapTaggedResponse.bad) { + _currentFolder = oldFolder; + } return response; } diff --git a/lib/src/imap_folder.dart b/lib/src/imap_folder.dart index 1ef9113..bd61052 100644 --- a/lib/src/imap_folder.dart +++ b/lib/src/imap_folder.dart @@ -403,11 +403,11 @@ class ImapFolder extends _ImapCommandable { while (word.type != ImapWordType.parenClose) { var value; // get value - if (word.type == ImapWordType.nil) + if (word.type == ImapWordType.nil) { value = null; - else if (word.type == ImapWordType.string) + } else if (word.type == ImapWordType.string) { value = word.value; - else if (word.type == ImapWordType.atom) { + } else if (word.type == ImapWordType.atom) { int number = int.tryParse(word.value); if (number != null) { value = number; From c1b15f71d55d9800128d04432765a500a333d2dc Mon Sep 17 00:00:00 2001 From: michaelspiss Date: Fri, 9 Aug 2019 16:19:48 +0200 Subject: [PATCH 3/5] Remove unused return values from anonymous command before functions --- lib/src/imap_client.dart | 16 +++++----------- lib/src/imap_command.dart | 2 +- lib/src/imap_commandable.dart | 10 +++++----- lib/src/imap_engine.dart | 1 + 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/lib/src/imap_client.dart b/lib/src/imap_client.dart index b7d7f23..339e577 100644 --- a/lib/src/imap_client.dart +++ b/lib/src/imap_client.dart @@ -52,8 +52,7 @@ class ImapClient extends _ImapCommandable { "LOGIN \"$username\" \"$password\"", before: () async { _requiresNotAuthenticated("LOGIN"); if (_engine.hasCapability("LOGINDISABLED")) { - _debugLog("Using LOGIN is forbidden by server"); - return ImapTaggedResponse.bad; + throw new UnsupportedException("Using LOGIN is forbidden by server"); } }); if (response == ImapTaggedResponse.ok) { @@ -77,10 +76,8 @@ class ImapClient extends _ImapCommandable { await sendCommand("AUTHENTICATE " + mechanismName, before: () async { _requiresNotAuthenticated("AUTHENTICATE"); if (!_engine._serverAuthCapabilities.contains(mechanismName)) { - _debugLog("AUTHENTICATE called with unsupported sasl mechanism \"" + - mechanism.name + - "\""); - return ImapTaggedResponse.bad; + throw new UnsupportedException( + 'AUTHENTICATE called with unsupported sasl mechanism "${mechanism.name}"'); } }, onContinue: (String response) { if (mechanism.isAuthenticated) { @@ -111,13 +108,10 @@ class ImapClient extends _ImapCommandable { _requiresNotAuthenticated("STARTTLS"); if (_secure) { _debugLog("starttls command used, but connection is already secure."); - return ImapTaggedResponse.bad; + return null; } if (!_engine.hasCapability("STARTTLS")) { - _debugLog( - "STARTTLS is not enabled. Maybe you have to do a capability " + - "request first."); - return ImapTaggedResponse.bad; + throw new UnsupportedException("STARTTLS is not enabled by the server"); } }); // Negotiate tls diff --git a/lib/src/imap_command.dart b/lib/src/imap_command.dart index 886a4a2..d222974 100644 --- a/lib/src/imap_command.dart +++ b/lib/src/imap_command.dart @@ -39,7 +39,7 @@ class ImapCommand { String get responseCode => _responseCode; /// Executed before run(), awaited for if async - void Function() _before; + Future Function() _before; /// Constructor for an ImapCommand. /// diff --git a/lib/src/imap_commandable.dart b/lib/src/imap_commandable.dart index ceed762..8a9170d 100644 --- a/lib/src/imap_commandable.dart +++ b/lib/src/imap_commandable.dart @@ -60,9 +60,9 @@ abstract class _ImapCommandable { /// This tells the server that this client would like to close the connection. /// The connection is then closed by the server. Future logout() async { - return sendCommand("LOGOUT").then((ImapTaggedResponse response) { - if (response == ImapTaggedResponse.ok) _engine._socket.close(); - }); + ImapTaggedResponse response = await sendCommand("LOGOUT"); + await _engine._socket.close(); + return response; } /// Sends "NOOP" command defined in rfc 3501 @@ -344,10 +344,10 @@ abstract class _ImapCommandable { } /// Makes sure the client is authenticated, throws [StateException] otherwise - void _requiresAuthenticated(String command) { + Future _requiresAuthenticated(String command) async { if (!_engine.isAuthenticated) { throw new StateException( - "Trying to use \"" + command + "\" in unauthenticated state."); + 'Trying to use "$command" in unauthenticated state.'); } } } diff --git a/lib/src/imap_engine.dart b/lib/src/imap_engine.dart index 247edf9..184ff5a 100644 --- a/lib/src/imap_engine.dart +++ b/lib/src/imap_engine.dart @@ -141,6 +141,7 @@ class ImapEngine { /// Checks if server has capability, also returns false if no data is present! bool hasCapability(String capability) { + // TODO: Automatically perform CAPABILITY request if completely empty return _capabilities.contains(capability); } From 6748ce5dcf236d7813b61c735d75b41a2dd699fa Mon Sep 17 00:00:00 2001 From: michaelspiss Date: Wed, 21 Aug 2019 02:17:28 +0200 Subject: [PATCH 4/5] Fix #15 by implementing utf-8 to utf-7 translation for folder names --- lib/imap_client.dart | 1 + lib/src/imap_commandable.dart | 33 +++++++++++++++++++++++++-------- lib/src/imap_engine.dart | 4 ++-- lib/src/imap_folder.dart | 5 ++++- pubspec.yaml | 1 + 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/lib/imap_client.dart b/lib/imap_client.dart index 35f2df7..54a1f39 100644 --- a/lib/imap_client.dart +++ b/lib/imap_client.dart @@ -7,6 +7,7 @@ import 'dart:collection'; import 'dart:convert'; import 'package:logging/logging.dart'; +import 'package:utf7/utf7.dart'; part "src/buffer_awaiter.dart"; diff --git a/lib/src/imap_commandable.dart b/lib/src/imap_commandable.dart index 8a9170d..a3a25ca 100644 --- a/lib/src/imap_commandable.dart +++ b/lib/src/imap_commandable.dart @@ -83,6 +83,7 @@ abstract class _ImapCommandable { /// server. /// Sends "CREATE" command, defined in rfc 3501 Future create(String folderName) { + folderName = _utf8ToModifiedUtf7(folderName); return sendCommand("CREATE \"" + folderName + "\"", before: () => _requiresAuthenticated("CREATE")); } @@ -93,7 +94,7 @@ abstract class _ImapCommandable { /// folder will no longer be selectable. /// Sends "DELETE" command, defined in rfc 3501 Future delete(ImapFolder folder) { - return sendCommand("DELETE \"" + folder.name + "\"", + return sendCommand('DELETE "${folder.serverName}"', before: () => _requiresAuthenticated("CREATE")); } @@ -101,11 +102,12 @@ abstract class _ImapCommandable { /// /// Sends "RENAME" command, defined in rfc 3501 Future rename(ImapFolder folder, String newName) async { + newName = _utf8ToModifiedUtf7(newName); ImapTaggedResponse response = await sendCommand( - "RENAME \"" + folder.name + "\" \"" + newName + "\"", + 'RENAME "${folder.serverName}" "$newName"', before: () => _requiresAuthenticated("CREATE")); if (response == ImapTaggedResponse.ok) { - _engine._folderCache.remove(folder.name); + _engine._folderCache.remove(folder.serverName); folder._name = newName; _engine._folderCache[newName] = folder; } @@ -177,7 +179,7 @@ abstract class _ImapCommandable { ImapFolder folder, Iterable dataItems) async { return sendCommand( "STATUS \"" + - folder.name + + folder.serverName + "\" (" + await _statusItemsToString(dataItems) + ")", @@ -227,7 +229,7 @@ abstract class _ImapCommandable { dateTime == null ? "" : " " + _dateTimeToString(dateTime); return sendCommand( "APPEND \"" + - folder.name + + folder.serverName + "\"" + flagsList + dateTimeString + @@ -242,7 +244,7 @@ abstract class _ImapCommandable { /// /// Sends "SUBSCRIBE" command, defined in rfc 3501 Future subscribe(ImapFolder folder) { - return sendCommand("SUBSCRIBE \"" + folder.name + "\"", + return sendCommand("SUBSCRIBE \"" + folder.serverName + "\"", before: () => _requiresAuthenticated("SUBSCRIBE")); } @@ -250,7 +252,7 @@ abstract class _ImapCommandable { /// /// Sends "UNSUBSCRIBE" command, defined in rfc 3501 Future unsubscribe(ImapFolder folder) { - return sendCommand("UNSUBSCRIBE \"" + folder.name + "\"", + return sendCommand("UNSUBSCRIBE \"" + folder.serverName + "\"", before: () => _requiresAuthenticated("UNSUBSCRIBE")); } @@ -288,7 +290,7 @@ abstract class _ImapCommandable { word = await buffer.readWord(); String hierarchyDelimiter = word.type == ImapWordType.nil ? null : word.value; - String name = (await buffer.readWord()).value; + String name = _modifiedUtf7ToUtf8((await buffer.readWord()).value); collector.add(new ImapListResponse(attributes, name, hierarchyDelimiter)); await buffer.skipLine(); } @@ -350,4 +352,19 @@ abstract class _ImapCommandable { 'Trying to use "$command" in unauthenticated state.'); } } + + static String _utf8ToModifiedUtf7(String input) { + return input + .replaceAll("&", "&-") + .replaceAllMapped(new RegExp(r"[^\x20-\x7e]+"), (Match match) { + return "&${Utf7.encodeModifiedBase64(match[0]).replaceAll("/", ",")}-"; + }); + } + + static String _modifiedUtf7ToUtf8(String input) { + return input.replaceAllMapped(new RegExp(r"&([^-]*)-"), (Match match) { + if (match[1].isEmpty) return "&"; + return Utf7.decodeModifiedBase64(match[1].replaceAll(",", "/")); + }); + } } diff --git a/lib/src/imap_engine.dart b/lib/src/imap_engine.dart index 184ff5a..a00f76c 100644 --- a/lib/src/imap_engine.dart +++ b/lib/src/imap_engine.dart @@ -124,9 +124,9 @@ class ImapEngine { if (folder == null) { select = new ImapCommand(this, folder, "CLOSE"); } else if (folder.isReadWrite == false) { - select = new ImapCommand(this, folder, "EXAMINE \"" + folder.name + "\""); + select = new ImapCommand(this, folder, 'EXAMINE "${folder.serverName}"'); } else { - select = new ImapCommand(this, folder, "SELECT \"" + folder.name + "\""); + select = new ImapCommand(this, folder, 'SELECT "${folder.serverName}"'); } _queue.addFirst(_currentInstruction); _queue.addFirst(select); diff --git a/lib/src/imap_folder.dart b/lib/src/imap_folder.dart index bd61052..c628e3b 100644 --- a/lib/src/imap_folder.dart +++ b/lib/src/imap_folder.dart @@ -5,7 +5,10 @@ class ImapFolder extends _ImapCommandable { /// Folder ("mailbox") name - acts as id String _name; - String get name => _name; + String get name => _ImapCommandable._utf8ToModifiedUtf7(_name); + + /// Utf-7 version of the folder's name + String get serverName => _ImapCommandable._utf8ToModifiedUtf7(_name); /// UIDVALIDITY attribute - uid for mailbox, defined in rfc 3501 int _uidvalidity; diff --git a/pubspec.yaml b/pubspec.yaml index 1ed7827..e9b0303 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ environment: dependencies: logging: ^0.11.3 pedantic: ^1.4.0 + utf7: ^0.1.0 dev_dependencies: test: ^1.3.0 From 839d3da2905d6691e1949f645cc1b6abf3fd1f81 Mon Sep 17 00:00:00 2001 From: michaelspiss Date: Wed, 21 Aug 2019 02:20:08 +0200 Subject: [PATCH 5/5] Bump version number, add changelog item --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e306154..11e71c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.6 + +- Fix #15 by translating folder names to utf-7 before sending it them to the server + ## 0.2.5 - Fix bodystructure parsing errors (issues #16, #18) diff --git a/pubspec.yaml b/pubspec.yaml index e9b0303..8d7fa4f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: imap_client description: IMAP (version 4rev1) implementation for dart -version: 0.2.5 +version: 0.2.6 homepage: https://github.com/michaelspiss/imap_client author: Michael Spiss documentation: