Skip to content

Commit

Permalink
Merge branch 'release/0.2.6'
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelspiss committed Aug 21, 2019
2 parents 543f280 + 839d3da commit 6ec21e9
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 39 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
1 change: 1 addition & 0 deletions lib/imap_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
10 changes: 6 additions & 4 deletions lib/src/imap_buffer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
16 changes: 5 additions & 11 deletions lib/src/imap_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/src/imap_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class ImapCommand {
String get responseCode => _responseCode;

/// Executed before run(), awaited for if async
void Function() _before;
Future<void> Function() _before;

/// Constructor for an ImapCommand.
///
Expand Down
43 changes: 30 additions & 13 deletions lib/src/imap_commandable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<ImapTaggedResponse> 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
Expand All @@ -83,6 +83,7 @@ abstract class _ImapCommandable {
/// server.
/// Sends "CREATE" command, defined in rfc 3501
Future<ImapTaggedResponse> create(String folderName) {
folderName = _utf8ToModifiedUtf7(folderName);
return sendCommand("CREATE \"" + folderName + "\"",
before: () => _requiresAuthenticated("CREATE"));
}
Expand All @@ -93,19 +94,20 @@ abstract class _ImapCommandable {
/// folder will no longer be selectable.
/// Sends "DELETE" command, defined in rfc 3501
Future<ImapTaggedResponse> delete(ImapFolder folder) {
return sendCommand("DELETE \"" + folder.name + "\"",
return sendCommand('DELETE "${folder.serverName}"',
before: () => _requiresAuthenticated("CREATE"));
}

/// Renames the given [folder] to [newName]
///
/// Sends "RENAME" command, defined in rfc 3501
Future<ImapTaggedResponse> 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;
}
Expand Down Expand Up @@ -177,7 +179,7 @@ abstract class _ImapCommandable {
ImapFolder folder, Iterable<ImapStatusDataItem> dataItems) async {
return sendCommand(
"STATUS \"" +
folder.name +
folder.serverName +
"\" (" +
await _statusItemsToString(dataItems) +
")",
Expand Down Expand Up @@ -227,7 +229,7 @@ abstract class _ImapCommandable {
dateTime == null ? "" : " " + _dateTimeToString(dateTime);
return sendCommand(
"APPEND \"" +
folder.name +
folder.serverName +
"\"" +
flagsList +
dateTimeString +
Expand All @@ -242,15 +244,15 @@ abstract class _ImapCommandable {
///
/// Sends "SUBSCRIBE" command, defined in rfc 3501
Future<ImapTaggedResponse> subscribe(ImapFolder folder) {
return sendCommand("SUBSCRIBE \"" + folder.name + "\"",
return sendCommand("SUBSCRIBE \"" + folder.serverName + "\"",
before: () => _requiresAuthenticated("SUBSCRIBE"));
}

/// Removes [folder] from subscribed/active list
///
/// Sends "UNSUBSCRIBE" command, defined in rfc 3501
Future<ImapTaggedResponse> unsubscribe(ImapFolder folder) {
return sendCommand("UNSUBSCRIBE \"" + folder.name + "\"",
return sendCommand("UNSUBSCRIBE \"" + folder.serverName + "\"",
before: () => _requiresAuthenticated("UNSUBSCRIBE"));
}

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -344,10 +346,25 @@ abstract class _ImapCommandable {
}

/// Makes sure the client is authenticated, throws [StateException] otherwise
void _requiresAuthenticated(String command) {
Future<void> _requiresAuthenticated(String command) async {
if (!_engine.isAuthenticated) {
throw new StateException(
"Trying to use \"" + command + "\" in unauthenticated state.");
'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(",", "/"));
});
}
}
11 changes: 7 additions & 4 deletions lib/src/imap_engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -124,21 +124,24 @@ 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);
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;
}

/// 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);
}

Expand Down
11 changes: 7 additions & 4 deletions lib/src/imap_folder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -403,11 +406,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;
Expand Down
5 changes: 3 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: imap_client
description: An interface to get emails via the imap protocol (version 4rev1)
version: 0.2.5
description: IMAP (version 4rev1) implementation for dart
version: 0.2.6
homepage: https://github.com/michaelspiss/imap_client
author: Michael Spiss <michaelspiss.dev@mailbox.org>
documentation:
Expand All @@ -11,6 +11,7 @@ environment:
dependencies:
logging: ^0.11.3
pedantic: ^1.4.0
utf7: ^0.1.0

dev_dependencies:
test: ^1.3.0
Expand Down

0 comments on commit 6ec21e9

Please sign in to comment.