Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
robert-virkus committed Jan 25, 2020
1 parent 7fa3fd8 commit b40d5d5
Show file tree
Hide file tree
Showing 53 changed files with 5,318 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Files and directories created by pub
.dart_tool/
.packages
# Remove the following pattern if you wish to check in your lock file
pubspec.lock

# Conventional directory for build outputs
build/

# Directory created by dartdoc
doc/api/

# Generated coverage data
coverage

3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 0.0.1

- Initial alpha version
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
An IMAP and SMTP client for Dart developers.

Available under the commercial friendly
[MPL Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/).

## Usage

A simple usage example:

```dart
import 'package:enough_mail/enough_mail.dart';
main() async {
var client = ImapClient(isLogEnabled: true);
await client.connectToServer('imap.example.com', 993, isSecure: true);
var loginResponse = await client.login('user.name', 'secret');
if (loginResponse.isOkStatus) {
var listResponse = await client.listMailboxes();
if (listResponse.isOkStatus) {
print('mailboxes: ${listResponse.result}');
}
}
}
```

## Features and bugs

Please file feature requests and bugs at the [issue tracker][tracker].

[tracker]: https://github.com/Enough-Software/enough_mail/issues
14 changes: 14 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Defines a default set of lint rules enforced for
# projects at Google. For details and rationale,
# see https://github.com/dart-lang/pedantic#enabled-lints.
include: package:pedantic/analysis_options.yaml

# For lint rules and documentation, see http://dart-lang.github.io/linter/lints.
# Uncomment to specify additional rules.
# linter:
# rules:
# - camel_case_types

analyzer:
# exclude:
# - path/to/excluded/files/**
14 changes: 14 additions & 0 deletions example/enough_mail_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:enough_mail/enough_mail.dart';

void main() async {
var client = ImapClient(isLogEnabled: true);
await client.connectToServer('imap.example.com', 993, isSecure: true);
var loginResponse = await client.login('user.name', 'secret');
if (loginResponse.isOkStatus) {
var listResponse = await client.listMailboxes();
if (listResponse.isOkStatus) {
print('mailboxes: ${listResponse.result}');
}
}

}
201 changes: 201 additions & 0 deletions lib/encodings.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import 'dart:convert';

class EncodingsHelper {
static const String utfEncodingStart = '=?utf-8?';
static const String utf8base64StartSequence = '=?utf-8?B?';
static const String utf8QencodingStartSequence = '=?utf-8?Q?';
static const String encodingEndSequence = '?=';

static String decodeAny(String input) {
if (input == null) {
return null;
}
var sequenceStart = input.indexOf(utfEncodingStart);
if (sequenceStart != -1) {
var startIndex = input.indexOf(utf8base64StartSequence, sequenceStart);
if (startIndex != -1) {
return _decode(
input, utf8base64StartSequence, startIndex, decodeUtfBase64Part);
} else {
startIndex = input.indexOf(utf8QencodingStartSequence, sequenceStart);
if (startIndex != -1) {
return _decode(input, utf8QencodingStartSequence, startIndex,
decodeQuotedPrintablePart);
}
}
}
return input;
}

static String decodeUtfBase64Part(String part) {
var outputList = base64.decode(part);
return String.fromCharCodes(outputList);
}

static String decodeQuotedPrintablePart(String part) {
var buffer = StringBuffer();
for (var i = 0; i < part.length; i++) {
var char = part[i];
if (char == '=') {
var hexText = part.substring(i + 1, i + 3);
var charCode = int.parse(hexText, radix: 16);
buffer.writeCharCode(charCode);
i += 2;
} else if (char == '_') {
buffer.write(' ');
} else {
buffer.write(char);
}
}
return buffer.toString();
}

static String _decode(String input, String startSequence, int startIndex,
String Function(String) decodePart) {
var endIndex =
input.indexOf(encodingEndSequence, startIndex + startSequence.length);
var buffer = StringBuffer();
if (startIndex > 0) {
buffer.write(input.substring(0, startIndex));
}
while (startIndex != -1 && endIndex != -1) {
var part = input.substring(startIndex + startSequence.length, endIndex);
buffer.write(decodePart(part));
startIndex =
input.indexOf(startSequence, endIndex + encodingEndSequence.length);
if (startIndex > endIndex + encodingEndSequence.length) {
buffer.write(
input.substring(endIndex + encodingEndSequence.length, startIndex));
} else if (startIndex == -1 &&
endIndex + encodingEndSequence.length < input.length) {
buffer.write(input.substring(endIndex + encodingEndSequence.length));
}
if (startIndex != -1) {
endIndex = input.indexOf(
encodingEndSequence, startIndex + startSequence.length);
}
}
return buffer.toString();
}

static String encodeDate(DateTime dateTime) {
/*
Date and time values occur in several header fields. This section
specifies the syntax for a full date and time specification. Though
folding white space is permitted throughout the date-time
specification, it is RECOMMENDED that a single space be used in each
place that FWS appears (whether it is required or optional); some
older implementations will not interpret longer sequences of folding
white space correctly.
date-time = [ day-of-week "," ] date time [CFWS]
day-of-week = ([FWS] day-name) / obs-day-of-week
day-name = "Mon" / "Tue" / "Wed" / "Thu" /
"Fri" / "Sat" / "Sun"
date = day month year
day = ([FWS] 1*2DIGIT FWS) / obs-day
month = "Jan" / "Feb" / "Mar" / "Apr" /
"May" / "Jun" / "Jul" / "Aug" /
"Sep" / "Oct" / "Nov" / "Dec"
year = (FWS 4*DIGIT FWS) / obs-year
time = time-of-day zone
time-of-day = hour ":" minute [ ":" second ]
hour = 2DIGIT / obs-hour
minute = 2DIGIT / obs-minute
second = 2DIGIT / obs-second
zone = (FWS ( "+" / "-" ) 4DIGIT) / obs-zone
The day is the numeric day of the month. The year is any numeric
year 1900 or later.
The time-of-day specifies the number of hours, minutes, and
optionally seconds since midnight of the date indicated.
The date and time-of-day SHOULD express local time.
The zone specifies the offset from Coordinated Universal Time (UTC,
formerly referred to as "Greenwich Mean Time") that the date and
time-of-day represent. The "+" or "-" indicates whether the time-of-
day is ahead of (i.e., east of) or behind (i.e., west of) Universal
Time. The first two digits indicate the number of hours difference
from Universal Time, and the last two digits indicate the number of
additional minutes difference from Universal Time. (Hence, +hhmm
means +(hh * 60 + mm) minutes, and -hhmm means -(hh * 60 + mm)
minutes). The form "+0000" SHOULD be used to indicate a time zone at
Universal Time. Though "-0000" also indicates Universal Time, it is
used to indicate that the time was generated on a system that may be
in a local time zone other than Universal Time and that the date-time
contains no information about the local time zone.
A date-time specification MUST be semantically valid. That is, the
day-of-week (if included) MUST be the day implied by the date, the
numeric day-of-month MUST be between 1 and the number of days allowed
for the specified month (in the specified year), the time-of-day MUST
be in the range 00:00:00 through 23:59:60 (the number of seconds
allowing for a leap second; see [RFC1305]), and the last two digits
of the zone MUST be within the range 00 through 59.
*/
var weekdays = <String>['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
var months = <String>[
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
];
var buffer = StringBuffer();
buffer.write(weekdays[dateTime.weekday - 1]);
buffer.write(', ');
buffer.write(dateTime.day);
buffer.write(' ');
buffer.write(months[dateTime.month - 1]);
buffer.write(' ');
buffer.write(dateTime.year);
buffer.write(' ');
buffer.write(dateTime.hour);
buffer.write(':');
buffer.write(dateTime.minute);
buffer.write(':');
buffer.write(dateTime.second);
buffer.write(' ');
if (dateTime.timeZoneOffset.inMinutes > 0) {
buffer.write('+');
} else {
buffer.write('-');
}
var hours = dateTime.timeZoneOffset.inHours;
if (hours < 10 && hours > -10) {
buffer.write('0');
}
buffer.write(hours);
var minutes = dateTime.timeZoneOffset.inMinutes -
(dateTime.timeZoneOffset.inHours * 60);
if (minutes == 0) {
buffer.write('00');
} else {
if (minutes < 10 && minutes > -10) {
buffer.write('0');
}
buffer.write(minutes);
}
return buffer.toString();
}
}
13 changes: 13 additions & 0 deletions lib/enough_mail.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
library enough_mail;

export 'imap/Address.dart';
export 'imap/Events.dart';
export 'imap/imap_client.dart';
export 'imap/Mailbox.dart';
export 'imap/Message.dart';
export 'imap/Response.dart';

export 'smtp/smtp_client.dart';
export 'smtp/smtp_response.dart';

export 'encodings.dart';
19 changes: 19 additions & 0 deletions lib/imap/address.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// An email address can consist of separate fields
class Address {
// personal name, [SMTP] at-domain-list (source route), mailbox name, and host name
String personalName;
String sourceRoute;
String mailboxName;
String hostName;

String _emailAddress;
String get emailAddress => _getEmailAddress();
set emailAddress (value) => _emailAddress = value;

Address.fromEnvelope(this.personalName, this.sourceRoute, this.mailboxName, this.hostName);

String _getEmailAddress() {
_emailAddress ??= '$mailboxName@$hostName';
return _emailAddress;
}
}
40 changes: 40 additions & 0 deletions lib/imap/events.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/// Classification of IMAP events
///
/// Compare [ImapEvent]
enum ImapEventType { expunge, fetch, exists, recent }

/// Base class for any event that can be fired by the IMAP client at any time.
class ImapEvent {
final ImapEventType eventType;
ImapEvent(this.eventType);
}

/// Notifies about a message that has been deleted
class ImapExpungeEvent extends ImapEvent {
int messageSequenceId;
ImapExpungeEvent(this.messageSequenceId) : super(ImapEventType.expunge);
}

/// Notifies about a message that has changed its status
class ImapFetchEvent extends ImapEvent {
int messageSequenceId;
List<String> flags; // TODO change to List<MessageFlag>
ImapFetchEvent(this.messageSequenceId, this.flags)
: super(ImapEventType.fetch);
}

/// Notifies about new messages
class ImapMessagesExistEvent extends ImapEvent {
int newMessagesExists;
int oldMessagesExists;
ImapMessagesExistEvent(this.newMessagesExists, this.oldMessagesExists)
: super(ImapEventType.exists);
}

/// Notifies about new messages
class ImapMessagesRecentEvent extends ImapEvent {
int newMessagesRecent;
int oldMessagesRecent;
ImapMessagesRecentEvent(this.newMessagesRecent, this.oldMessagesRecent)
: super(ImapEventType.recent);
}
Loading

0 comments on commit b40d5d5

Please sign in to comment.