Skip to content

Commit

Permalink
Merge branch 'release/0.2.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelspiss committed Mar 26, 2019
2 parents 1d96b86 + 3bd882c commit 2fc3517
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 55 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.2.1

- Fix #4, bug that prevented mailboxes with spaces from being opened
- Fix #7, bug that did not acknowledge escaped characters
- Fix #8, add missing imap 4 rev 1 commands (subscribe, unsubscribe, lsub)

## 0.2.0

- Version change, 1.0.0-alpha is lower than 0.1.3, which causes updates to fail
Expand Down
83 changes: 47 additions & 36 deletions lib/src/imap_buffer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ class ImapBuffer {

/// Contains all chars that have special token types
static const Map<int, ImapWordType> _specialChars = const <int, ImapWordType>{
10: ImapWordType.eol, // "\n"
43: ImapWordType.tokenPlus, // "+"
42: ImapWordType.tokenAsterisk, // "*"
40: ImapWordType.parenOpen, // "("
41: ImapWordType.parenClose, // ")"
91: ImapWordType.bracketOpen, // "["
93: ImapWordType.bracketClose, // "]"
10: ImapWordType.eol, // \n
43: ImapWordType.tokenPlus, // +
42: ImapWordType.tokenAsterisk, // *
40: ImapWordType.parenOpen, // (
41: ImapWordType.parenClose, // )
91: ImapWordType.bracketOpen, // [
93: ImapWordType.bracketClose, // ]
};

/// All characters considered whitespaces
static const List<int> _whitespaceChars = const [
32, // " "
9, // "\t"
13 // "\r"
32, // " " (space)
9, // \t
13 // \r
];

/// Adds data to the buffer
Expand All @@ -45,7 +45,7 @@ class ImapBuffer {
Future<String> readLine({autoReleaseBuffer = true}) async {
List<int> charCodes = <int>[];
while (await _isWhitespace()) _bufferPosition++;
while (await _getCharCode() != 10) // "\n"
while (await _getCharCode() != 10) // \n
charCodes.add(await _getCharCode(proceed: true));
_bufferPosition++; // skip over newline character
// trim trailing whitespaces
Expand All @@ -59,7 +59,7 @@ class ImapBuffer {

/// Skips all characters in this line
Future<void> skipLine({autoReleaseBuffer = true}) async {
while (await _getCharCode(proceed: true) != 10); // "skip until behind \n"
while (await _getCharCode(proceed: true) != 10); // skip until behind \n
if (autoReleaseBuffer) _releaseUsedBuffer();
return;
}
Expand All @@ -76,11 +76,11 @@ class ImapBuffer {
if (_specialChars.containsKey(charAtPosition))
word = ImapWord(_specialChars[charAtPosition],
String.fromCharCode(await _getCharCode(proceed: true)));
else if (charAtPosition == 34) // "\""
else if (charAtPosition == 34) // "
word = await readQuotedString(autoReleaseBuffer: false);
else if (charAtPosition == 123) // "{"
else if (charAtPosition == 123) // {
word = await readLiteral(autoReleaseBuffer: false);
else if (charAtPosition == 92) // "\\"
else if (charAtPosition == 92) // \
word = await readFlag(autoReleaseBuffer: false);
else
word = await readAtom(autoReleaseBuffer: false);
Expand All @@ -93,27 +93,40 @@ class ImapBuffer {

/// Reads a quoted string starting at the current [_bufferPosition]
///
/// Must start with "\""
/// Must start with "
Future<ImapWord> readQuotedString({autoReleaseBuffer = true}) async {
while (await _isWhitespace()) _bufferPosition++;
if (await _getCharCode() != 34) // "\""
if (await _getCharCode() != 34) // "
throw new InvalidFormatException(
"Expected quote at beginning of quoted string");
_bufferPosition++;
List<int> charCodes = <int>[];
while (await _getCharCode() != 34) // "\""
charCodes.add(await _getCharCode(proceed: true));
_bufferPosition++; // move behind closing quote
int nextChar = await _getCharCode(proceed: true);
while (nextChar != 34 /* " */) {
charCodes.add(nextChar);
nextChar = await _getCharCode(proceed: true);
if (nextChar == 92 /* \ */) {
nextChar = await _getCharCode(proceed: true); // skip first backslash
if (nextChar == 92 /* \ */ || nextChar == 34 /* " */) {
charCodes.add(nextChar);
nextChar = await _getCharCode(proceed: true); // skip first backslash
} else {
// bad format, only escaped backslash or quotation mark are supported
throw new SyntaxErrorException(
"Unknown escape sequence \\${String.fromCharCode(nextChar)}");
}
}
}
if (autoReleaseBuffer) _releaseUsedBuffer();
return new ImapWord(ImapWordType.string, String.fromCharCodes(charCodes));
}

/// Reads a literal starting at the current [_bufferPosition]
///
/// Must start with "{"
/// Must start with {
Future<ImapWord> readLiteral({autoReleaseBuffer = true}) async {
while (await _isWhitespace()) _bufferPosition++;
if (await _getCharCode() != 123) // "{"
if (await _getCharCode() != 123) // {
throw new InvalidFormatException(
"Expected open curly bracket at beginning of literal");
_bufferPosition++;
Expand All @@ -134,10 +147,10 @@ class ImapBuffer {

/// Reads a flag starting at the current [_bufferPosition]
///
/// Must start with "\\"
/// Must start with \
Future<ImapWord> readFlag({autoReleaseBuffer = true}) async {
while (await _isWhitespace()) _bufferPosition++;
if (await _getCharCode() != 92) // "\\"
if (await _getCharCode() != 92) // \
throw new InvalidFormatException("Expected \\ before flag name");
_bufferPosition++;
List<int> charCodes = <int>[92];
Expand Down Expand Up @@ -218,18 +231,16 @@ class ImapBuffer {
Future<bool> _isValidAtomCharCode([int charCode = -1]) async {
if (charCode == -1) charCode = await _getCharCode();
if (charCode <= 31 || // CTL
charCode == 34 || // "\""
charCode == 37 || // "%"
charCode == 40 || // "("
charCode == 41 || // ")"
charCode == 42 || // "*"
charCode == 92 || // "\\"
charCode == 93 || // "]"
charCode == 123 || // "{"
charCode == 127) {
// CTL
return false;
}
charCode == 34 || // "
charCode == 37 || // %
charCode == 40 || // (
charCode == 41 || // )
charCode == 42 || // *
charCode == 92 || // \
charCode == 93 || // ]
charCode == 123 || // {
charCode == 127 // CTL
) return false;
return true;
}

Expand Down
72 changes: 56 additions & 16 deletions lib/src/imap_commandable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -143,22 +143,8 @@ abstract class _ImapCommandable {
"LIST \"" + referenceName + "\" \"" + folderName + "\"",
before: () => _requiresAuthenticated("LIST"),
untaggedHandlers: {
"LIST": (ImapBuffer buffer, {int number}) async {
await buffer.readWord(expected: ImapWordType.parenOpen);
ImapWord word = await buffer.readWord();
List<String> attributes = [];
while (word.type != ImapWordType.parenClose) {
attributes.add(word.value);
word = await buffer.readWord();
}
word = await buffer.readWord();
String hierarchyDelimiter =
word.type == ImapWordType.nil ? null : word.value;
String name = (await buffer.readWord()).value;
list.add(
new ImapListResponse(attributes, name, hierarchyDelimiter));
await buffer.skipLine();
}
"LIST": (ImapBuffer buffer, {int number}) async =>
await _listUntaggedHandler(buffer, list)
});
if (response != ImapTaggedResponse.ok)
throw new ArgumentError("Reference or name cannot be listed.");
Expand Down Expand Up @@ -233,6 +219,60 @@ abstract class _ImapCommandable {
onContinue: (String serverData) => message);
}

/// Adds [folder] to subscribed/active list
///
/// Sends "SUBSCRIBE" command, defined in rfc 3501
Future<ImapTaggedResponse> subscribe(ImapFolder folder) {
return sendCommand("SUBSCRIBE \"" + folder.name + "\"",
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 + "\"",
before: () => _requiresAuthenticated("UNSUBSCRIBE"));
}

/// Same as [list], but limited to folders in subscribed/active list
///
/// For information on parameters see [list], to add folders to the
/// subscribed/active list see [subscribe] and to remove [unsubscribe].
/// Sends "LSUB" command, defined in rfc 3501
Future<List<ImapListResponse>> lsub(String folderName,
{String referenceName = ""}) async {
List<ImapListResponse> list = [];
ImapTaggedResponse response = await sendCommand(
"LSUB \"" + referenceName + "\" \"" + folderName + "\"",
before: () => _requiresAuthenticated("LSUB"),
untaggedHandlers: {
"LSUB": (ImapBuffer buffer, {int number}) async =>
await _listUntaggedHandler(buffer, list)
});
if (response != ImapTaggedResponse.ok)
throw new ArgumentError("Reference or name cannot be listed.");
return list;
}

/// Adds items returned by [lsub] or [list] to [collector].
static void _listUntaggedHandler(
ImapBuffer buffer, List<ImapListResponse> collector) async {
await buffer.readWord(expected: ImapWordType.parenOpen);
ImapWord word = await buffer.readWord();
List<String> attributes = [];
while (word.type != ImapWordType.parenClose) {
attributes.add(word.value);
word = await buffer.readWord();
}
word = await buffer.readWord();
String hierarchyDelimiter =
word.type == ImapWordType.nil ? null : word.value;
String name = (await buffer.readWord()).value;
collector.add(new ImapListResponse(attributes, name, hierarchyDelimiter));
await buffer.skipLine();
}

/// Converts [DateTime] to imap date
static String _dateTimeToString(DateTime dateTime) {
StringBuffer buffer = new StringBuffer("\"");
Expand Down
4 changes: 2 additions & 2 deletions lib/src/imap_engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,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.name + "\"");
else
select = new ImapCommand(this, folder, "SELECT " + folder.name);
select = new ImapCommand(this, folder, "SELECT \"" + folder.name + "\"");
_queue.addFirst(_currentInstruction);
_queue.addFirst(select);
ImapTaggedResponse response = await executeCommand(select);
Expand Down
2 changes: 1 addition & 1 deletion 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.0
version: 0.2.1
homepage: https://github.com/michaelspiss/imap_client
author: Michael Spiss <michaelspiss.dev@mailbox.org>
documentation:
Expand Down
17 changes: 17 additions & 0 deletions test/imap_buffer_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,23 @@ void main() {
expect(await () async => await _buffer.readQuotedString(),
throwsA(new TypeMatcher<InvalidFormatException>()));
});
test('String may countain escaped quotation mark', () async {
var list = r'"A \"quotation mark\""'.codeUnits;
_controller.add(list);
expect((await _buffer.readQuotedString()).value, 'A "quotation mark"');
});
test('String may countain escaped backslash', () async {
var list = r'"A \\ backslash \\"'.codeUnits;
_controller.add(list);
expect((await _buffer.readQuotedString()).value, r'A \ backslash \');
});
test('Throws SyntaxErrorException if a characeter besides ", \\ is escaped',
() async {
var list = r'"A bad \format"'.codeUnits;
_controller.add(list);
expect(await () async => await _buffer.readQuotedString(),
throwsA(new TypeMatcher<SyntaxErrorException>()));
});
});

group("readLiteral tests", () {
Expand Down

0 comments on commit 2fc3517

Please sign in to comment.