diff --git a/Makefile b/Makefile index 40c1ccb6..1e20cc5b 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ ENGINE_SRC := \ src/engine/state/Machine.vala \ src/engine/state/MachineDescriptor.vala \ src/engine/state/Mapping.vala \ + src/engine/common/MessageData.vala \ src/engine/imap/ClientConnection.vala \ src/engine/imap/ClientSession.vala \ src/engine/imap/Mailbox.vala \ @@ -26,10 +27,15 @@ ENGINE_SRC := \ src/engine/imap/ServerData.vala \ src/engine/imap/Status.vala \ src/engine/imap/CommandResponse.vala \ + src/engine/imap/FetchResults.vala \ + src/engine/imap/FetchDataDecoder.vala \ + src/engine/imap/MessageData.vala \ src/engine/imap/Serializable.vala \ src/engine/imap/Serializer.vala \ src/engine/imap/Deserializer.vala \ src/engine/imap/Error.vala \ + src/engine/rfc822/MailboxAddress.vala \ + src/engine/rfc822/MessageData.vala \ src/engine/util/string.vala CONSOLE_SRC := \ diff --git a/src/console/main.vala b/src/console/main.vala index 718b80cf..cbffa8ea 100644 --- a/src/console/main.vala +++ b/src/console/main.vala @@ -102,7 +102,7 @@ class ImapConsole : Gtk.Window { string[] args = new string[0]; for (int ctr = 1; ctr < tokens.length; ctr++) { string arg = tokens[ctr].strip(); - if (!is_empty_string(arg)) + if (!String.is_empty(arg)) args += arg; } diff --git a/src/engine/Message.vala b/src/engine/Message.vala index 62527f22..b87364e7 100644 --- a/src/engine/Message.vala +++ b/src/engine/Message.vala @@ -6,11 +6,12 @@ public class Geary.Message { public int msg_num { get; private set; } - public string from { get; private set; } - public string subject { get; private set; } - public string sent { get; private set; } + public Geary.RFC822.MailboxAddresses from { get; private set; } + public Geary.RFC822.Subject subject { get; private set; } + public Geary.RFC822.Date sent { get; private set; } - public Message(int msg_num, string from, string subject, string sent) { + public Message(int msg_num, Geary.RFC822.MailboxAddresses from, Geary.RFC822.Subject subject, + Geary.RFC822.Date sent) { this.msg_num = msg_num; this.from = from; this.subject = subject; @@ -18,7 +19,7 @@ public class Geary.Message { } public string to_string() { - return "[%d] %s: %s (%s)".printf(msg_num, from, subject, sent); + return "[%d] %s: %s (%s)".printf(msg_num, from.to_string(), subject.to_string(), sent.to_string()); } } diff --git a/src/engine/common/MessageData.vala b/src/engine/common/MessageData.vala new file mode 100644 index 00000000..6477a9de --- /dev/null +++ b/src/engine/common/MessageData.vala @@ -0,0 +1,73 @@ +/* Copyright 2011 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Common.MessageData is an abstract base class to unify the various message-related data and + * metadata that may be associated with a mail message, whether it's embedded in its MIME + * structure, its RFC822 header, IMAP metadata, or details from a POP server. + */ + +public abstract class Geary.Common.MessageData { + /** + * to_string() is intended for debugging and logging purposes, not user-visible text or + * serialization. + */ + public abstract string to_string(); + + /** + * emit() is intended for sending the data on the wire in a textual format. + */ + public abstract string emit(); +} + +public abstract class Geary.Common.StringMessageData : Geary.Common.MessageData { + public string value { get; private set; } + + public StringMessageData(string value) { + this.value = value; + } + + public override string to_string() { + return value; + } + + public override string emit() { + return value; + } +} + +public abstract class Geary.Common.IntMessageData : Geary.Common.MessageData { + public int value { get; private set; } + + public IntMessageData(int value) { + this.value = value; + } + + public override string to_string() { + return value.to_string(); + } + + public override string emit() { + return value.to_string(); + } +} + +public abstract class Geary.Common.LongMessageData : Geary.Common.MessageData { + public long value { get; private set; } + + public LongMessageData(long value) { + this.value = value; + } + + public override string to_string() { + return value.to_string(); + } + + public override string emit() { + return value.to_string(); + } +} + diff --git a/src/engine/imap/ClientSession.vala b/src/engine/imap/ClientSession.vala index 3f64c322..b3b66565 100644 --- a/src/engine/imap/ClientSession.vala +++ b/src/engine/imap/ClientSession.vala @@ -279,7 +279,7 @@ public class Geary.Imap.ClientSession : Object, Geary.Account { }; fsm = new Geary.State.Machine(machine_desc, mappings, on_ignored_transition); - fsm.set_logging(true); + fsm.set_logging(false); } public Tag? generate_tag() { diff --git a/src/engine/imap/Deserializer.vala b/src/engine/imap/Deserializer.vala index 2bad91a4..35e85f85 100644 --- a/src/engine/imap/Deserializer.vala +++ b/src/engine/imap/Deserializer.vala @@ -258,7 +258,7 @@ public class Geary.Imap.Deserializer { } private bool is_current_string_empty() { - return (current_string == null) || is_empty_string(current_string.str); + return (current_string == null) || String.is_empty(current_string.str); } private void append_to_string(unichar ch) { @@ -279,7 +279,11 @@ public class Geary.Imap.Deserializer { if (is_current_string_empty()) return; - save_parameter(new StringParameter(current_string.str)); + if (NilParameter.is_nil(current_string.str)) + save_parameter(NilParameter.instance); + else + save_parameter(new StringParameter(current_string.str)); + current_string = null; } diff --git a/src/engine/imap/FetchCommand.vala b/src/engine/imap/FetchCommand.vala index c32baf59..264a888c 100644 --- a/src/engine/imap/FetchCommand.vala +++ b/src/engine/imap/FetchCommand.vala @@ -4,6 +4,7 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ +// TODO: Support body[section] and body.peek[section] forms public enum Geary.Imap.FetchDataItem { UID, FLAGS, @@ -118,6 +119,29 @@ public enum Geary.Imap.FetchDataItem { public static FetchDataItem from_parameter(StringParameter strparam) throws ImapError { return decode(strparam.value); } + + public FetchDataDecoder? get_decoder() { + switch (this) { + case UID: + return new UIDDecoder(); + + case FLAGS: + return new FlagsDecoder(); + + case ENVELOPE: + return new EnvelopeDecoder(); + + case INTERNALDATE: + return new InternalDateDecoder(); + + case RFC822_SIZE: + return new RFC822SizeDecoder(); + + + default: + return null; + } + } } public class Geary.Imap.FetchCommand : Command { diff --git a/src/engine/imap/FetchDataDecoder.vala b/src/engine/imap/FetchDataDecoder.vala new file mode 100644 index 00000000..2181be2d --- /dev/null +++ b/src/engine/imap/FetchDataDecoder.vala @@ -0,0 +1,149 @@ +/* Copyright 2011 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * FetchDataDecoder accept the result line of a FETCH response and decodes it into MessageData. + * While they can be used standalone, they're intended to be used by FetchResults to process + * a CommandResponse. + * + * Note that FetchDataDecoders are keyed off of FetchDataItem; new implementations should add + * themselves to FetchDataItem.get_decoder(). + * + * In the future FetchDataDecoder may be used to decode MessageData stored in other formats, such + * as in a database. + */ + +public abstract class Geary.Imap.FetchDataDecoder { + public FetchDataItem data_item { get; private set; } + + public FetchDataDecoder(FetchDataItem data_item) { + this.data_item = data_item; + } + + // The default implementation determines the type of the parameter and calls the appropriate + // virtual function; most implementations of a FetchResponseDecoder shouldn't need to override + // this method. + public virtual MessageData decode(Parameter param) throws ImapError { + StringParameter? stringp = param as StringParameter; + if (stringp != null) + return decode_string(stringp); + + ListParameter? listp = param as ListParameter; + if (listp != null) + return decode_list(listp); + + LiteralParameter? literalp = param as LiteralParameter; + if (literalp != null) + return decode_literal(literalp); + + throw new ImapError.TYPE_ERROR("Data item \"%s\" of unknown type", param.to_string()); + } + + public virtual MessageData decode_string(StringParameter param) throws ImapError { + throw new ImapError.TYPE_ERROR("%s does not accept a string parameter", data_item.to_string()); + } + + public virtual MessageData decode_list(ListParameter list) throws ImapError { + throw new ImapError.TYPE_ERROR("%s does not accept a list parameter", data_item.to_string()); + } + + public virtual MessageData decode_literal(LiteralParameter literal) throws ImapError { + throw new ImapError.TYPE_ERROR("%s does not accept a literal parameter", data_item.to_string()); + } +} + +public class Geary.Imap.UIDDecoder : Geary.Imap.FetchDataDecoder { + public UIDDecoder() { + base (FetchDataItem.UID); + } + + public override MessageData decode_string(StringParameter stringp) throws ImapError { + return new UID(stringp.as_int()); + } +} + +public class Geary.Imap.FlagsDecoder : Geary.Imap.FetchDataDecoder { + public FlagsDecoder() { + base (FetchDataItem.FLAGS); + } + + public override MessageData decode_list(ListParameter listp) throws ImapError { + Gee.List flags = new Gee.ArrayList(); + for (int ctr = 0; ctr < listp.get_count(); ctr++) + flags.add(new Flag(listp.get_as_string(ctr).value)); + + return new Flags(flags); + } +} + +public class Geary.Imap.InternalDateDecoder : Geary.Imap.FetchDataDecoder { + public InternalDateDecoder() { + base (FetchDataItem.INTERNALDATE); + } + + public override MessageData decode_string(StringParameter stringp) throws ImapError { + return new InternalDate(stringp.value); + } +} + +public class Geary.Imap.RFC822SizeDecoder : Geary.Imap.FetchDataDecoder { + public RFC822SizeDecoder() { + base (FetchDataItem.RFC822_SIZE); + } + + public override MessageData decode_string(StringParameter stringp) throws ImapError { + return new RFC822Size(stringp.as_long()); + } +} + +public class Geary.Imap.EnvelopeDecoder : Geary.Imap.FetchDataDecoder { + public EnvelopeDecoder() { + base (FetchDataItem.ENVELOPE); + } + + // TODO: This doesn't handle group lists (see Johnson, p.268) + public override MessageData decode_list(ListParameter listp) throws ImapError { + StringParameter sent = listp.get_as_string(0); + StringParameter subject = listp.get_as_string(1); + ListParameter from = listp.get_as_list(2); + ListParameter sender = listp.get_as_list(3); + ListParameter? reply_to = listp.get_as_list(4); + ListParameter? to = listp.get_as_nullable_list(5); + ListParameter? cc = listp.get_as_nullable_list(6); + ListParameter? bcc = listp.get_as_nullable_list(7); + StringParameter? in_reply_to = listp.get_as_nullable_string(8); + StringParameter message_id = listp.get_as_string(9); + + return new Envelope(new Geary.RFC822.Date(sent.value), new Geary.RFC822.Subject(subject.value), + parse_addresses(from), parse_addresses(sender), parse_addresses(reply_to), + (to != null) ? parse_addresses(to) : null, + (cc != null) ? parse_addresses(cc) : null, + (bcc != null) ? parse_addresses(bcc) : null, + (in_reply_to != null) ? new Geary.RFC822.MessageID(in_reply_to.value) : null, + new Geary.RFC822.MessageID(message_id.value)); + } + + private Geary.RFC822.MailboxAddresses parse_addresses(ListParameter listp) throws ImapError { + Gee.List list = new Gee.ArrayList(); + for (int ctr = 0; ctr < listp.get_count(); ctr++) { + ListParameter fields = listp.get_as_list(ctr); + StringParameter? name = fields.get_as_nullable_string(0); + StringParameter? source_route = fields.get_as_nullable_string(1); + StringParameter mailbox = fields.get_as_string(2); + StringParameter domain = fields.get_as_string(3); + + Geary.RFC822.MailboxAddress addr = new Geary.RFC822.MailboxAddress( + (name != null) ? name.nullable_value : null, + (source_route != null) ? source_route.nullable_value : null, + mailbox.value, + domain.value); + list.add(addr); + } + + return new Geary.RFC822.MailboxAddresses(list); + } +} + diff --git a/src/engine/imap/FetchResults.vala b/src/engine/imap/FetchResults.vala new file mode 100644 index 00000000..b9cf0091 --- /dev/null +++ b/src/engine/imap/FetchResults.vala @@ -0,0 +1,76 @@ +/* Copyright 2011 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * FetchResults represents the data returned from a FETCH response for each message. Since + * FETCH allows for multiple FetchDataItems to be requested, this object can hold all of them. + * + * decode_command_response() will take a CommandResponse for a FETCH command and return all + * results for all messages specified. + */ + +public class Geary.Imap.FetchResults { + public int msg_num { get; private set; } + + private Gee.Map map = new Gee.HashMap(); + + public FetchResults(int msg_num) { + this.msg_num = msg_num; + } + + public static FetchResults decode(ServerData data) throws ImapError { + StringParameter msg_num = (StringParameter) data.get_as(1, typeof(StringParameter)); + StringParameter cmd = (StringParameter) data.get_as(2, typeof(StringParameter)); + ListParameter list = (ListParameter) data.get_as(3, typeof(ListParameter)); + + // verify this is a FETCH response + if (!cmd.equals_ci("fetch")) { + throw new ImapError.TYPE_ERROR("Unable to decode fetch response \"%s\": Not marked as fetch response", + data.to_string()); + } + + FetchResults results = new FetchResults(msg_num.as_int()); + + // walk the list for each returned fetch data item, which is paired by its data item name + // and the structured data itself + for (int ctr = 0; ctr < list.get_count(); ctr += 2) { + StringParameter data_item_param = (StringParameter) list.get_as(ctr, typeof(StringParameter)); + FetchDataItem data_item = FetchDataItem.decode(data_item_param.value); + FetchDataDecoder? decoder = data_item.get_decoder(); + if (decoder == null) { + debug("Unable to decode fetch response for \"%s\": No decoder available", + data_item.to_string()); + + continue; + } + + results.set_data(data_item, decoder.decode(list.get_required(ctr + 1))); + } + + return results; + } + + public static FetchResults[] decode_command_response(CommandResponse response) throws ImapError { + FetchResults[] array = new FetchResults[0]; + foreach (ServerData data in response.server_data) + array += decode(data); + + return array; + } + + public void set_data(FetchDataItem data_item, MessageData primitive) { + map.set(data_item, primitive); + } + + public MessageData? get_data(FetchDataItem data_item) { + return map.get(data_item); + } + + public int get_count() { + return map.size; + } +} + diff --git a/src/engine/imap/Mailbox.vala b/src/engine/imap/Mailbox.vala index 54fe308b..6d6ffa73 100644 --- a/src/engine/imap/Mailbox.vala +++ b/src/engine/imap/Mailbox.vala @@ -38,31 +38,11 @@ private class Geary.Imap.MessageStreamImpl : Object, Geary.MessageStream { throw new ImapError.SERVER_ERROR(resp.status_response.text); Gee.List msgs = new Gee.ArrayList(); - foreach (ServerData data in resp.server_data) { - StringParameter? label = data.get(2) as StringParameter; - if (label == null || label.value.down() != "fetch") { - debug("Not fetch data: %s", (label == null) ? "(null)" : label.value); - continue; - } - - StringParameter msg_num = (StringParameter) data.get_as(1, typeof(StringParameter)); - ListParameter envelope = (ListParameter) data.get_as(3, typeof(ListParameter)); - - ListParameter fields = (ListParameter) envelope.get_as(1, typeof(ListParameter)); - - StringParameter date = (StringParameter) fields.get_as(0, typeof(StringParameter)); - StringParameter subject = (StringParameter) fields.get_as(1, typeof(StringParameter)); - - ListParameter from_fields = (ListParameter) fields.get_as(3, typeof(ListParameter)); - ListParameter first_from = (ListParameter) from_fields.get_as(0, typeof(ListParameter)); - StringParameter from_name = (StringParameter) first_from.get_as(0, typeof(StringParameter)); - StringParameter from_mailbox = (StringParameter) first_from.get_as(2, typeof(StringParameter)); - StringParameter from_domain = (StringParameter) first_from.get_as(3, typeof(StringParameter)); - - Message msg = new Message(int.parse(msg_num.value), - "%s <%s@%s>".printf(from_name.value, from_mailbox.value, from_domain.value), - subject.value, date.value); - msgs.add(msg); + + FetchResults[] results = FetchResults.decode_command_response(resp); + foreach (FetchResults res in results) { + Envelope envelope = (Envelope) res.get_data(FetchDataItem.ENVELOPE); + msgs.add(new Message(res.msg_num, envelope.from, envelope.subject, envelope.sent)); } return msgs; diff --git a/src/engine/imap/MessageData.vala b/src/engine/imap/MessageData.vala new file mode 100644 index 00000000..c6d0f2f7 --- /dev/null +++ b/src/engine/imap/MessageData.vala @@ -0,0 +1,164 @@ +/* Copyright 2011 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * MessageData is an IMAP data structure delivered in some form by the server to the client. + * Although the primary use of this object is for FETCH results, other commands return + * similarly-structured data (in particulars Flags and Attributes). + * + * Note that IMAP specifies that Flags and Attributes are *always* returned as a list, even if only + * one is present, which is why these elements are MessageData but not the elements within the + * lists (Flag, Attribute). Obviously these classes are closely related, hence their presence + * here. + * + * Also note that Imap.MessageData inherits from Common.MessageData. + */ + +public interface Geary.Imap.MessageData : Geary.Common.MessageData { +} + +public class Geary.Imap.UID : Geary.Common.IntMessageData, Geary.Imap.MessageData { + public UID(int value) { + base (value); + } +} + +public class Geary.Imap.Flag { + public static Flag ANSWERED = new Flag("\\answered"); + public static Flag DELETED = new Flag("\\deleted"); + public static Flag DRAFT = new Flag("\\draft"); + public static Flag FLAGGED = new Flag("\\flagged"); + public static Flag RECENT = new Flag("\\recent"); + public static Flag SEEN = new Flag("\\seen"); + + public string value { get; private set; } + + public Flag(string value) { + this.value = value; + } + + public bool is_system() { + return value[0] == '\\'; + } + + public bool has_value(string value) { + return this.value.down() == value.down(); + } + + public bool equals(Flag flag) { + return (flag == this) ? true : flag.has_value(value); + } + + public string to_string() { + return value; + } + + public static uint hash_func(void *flag) { + return str_hash(((Flag *) flag)->value); + } + + public static bool equal_func(void *a, void *b) { + return ((Flag *) a)->equals((Flag *) b); + } +} + +public class Geary.Imap.Flags : Geary.Common.MessageData, Geary.Imap.MessageData { + private Gee.Set list; + + public Flags(Gee.Collection flags) { + list = new Gee.HashSet(Flag.hash_func, Flag.equal_func); + list.add_all(flags); + } + + public bool contains(Flag flag) { + return list.contains(flag); + } + + public Gee.Set get_all() { + return list.read_only_view; + } + + public override string to_string() { + return emit(); + } + + public override string emit() { + StringBuilder builder = new StringBuilder(); + foreach (Flag flag in list) { + if (!String.is_empty(builder.str)) + builder.append_c(' '); + + builder.append(flag.value); + } + + return builder.str; + } +} + +public class Geary.Imap.InternalDate : Geary.RFC822.Date, Geary.Imap.MessageData { + public InternalDate(string value) { + base (value); + } +} + +public class Geary.Imap.RFC822Size : Geary.RFC822.Size, Geary.Imap.MessageData { + public RFC822Size(long value) { + base (value); + } +} + +public class Geary.Imap.Envelope : Geary.Common.MessageData, Geary.Imap.MessageData { + public Geary.RFC822.Date sent { get; private set; } + public Geary.RFC822.Subject subject { get; private set; } + public Geary.RFC822.MailboxAddresses from { get; private set; } + public Geary.RFC822.MailboxAddresses sender { get; private set; } + public Geary.RFC822.MailboxAddresses? reply_to { get; private set; } + public Geary.RFC822.MailboxAddresses? to { get; private set; } + public Geary.RFC822.MailboxAddresses? cc { get; private set; } + public Geary.RFC822.MailboxAddresses? bcc { get; private set; } + public Geary.RFC822.MessageID? in_reply_to { get; private set; } + public Geary.RFC822.MessageID message_id { get; private set; } + + public Envelope(Geary.RFC822.Date sent, Geary.RFC822.Subject subject, + Geary.RFC822.MailboxAddresses from, Geary.RFC822.MailboxAddresses sender, + Geary.RFC822.MailboxAddresses? reply_to, Geary.RFC822.MailboxAddresses? to, + Geary.RFC822.MailboxAddresses? cc, Geary.RFC822.MailboxAddresses? bcc, + Geary.RFC822.MessageID? in_reply_to, Geary.RFC822.MessageID message_id) { + this.sent = sent; + this.subject = subject; + this.from = from; + this.sender = sender; + this.reply_to = reply_to; + this.to = to; + this.cc = cc; + this.bcc = bcc; + this.in_reply_to = in_reply_to; + this.message_id = message_id; + } + + public override string to_string() { + return "[%s] %s: \"%s\"".printf(sent.to_string(), from.to_string(), subject.to_string()); + } + + public override string emit() { + return to_string(); + } +} + +/* +public class Geary.Imap.BodyStructure : Geary.Imap.Primitive { +} + +public class Geary.Imap.RFC822Header : Geary.Imap.Primitive { +} + +public class Geary.Imap.RFC822Text : Geary.Imap.Primitive { +} + +public class Geary.Imap.RFC822 : Geary.Imap.Primitive { +} +*/ + diff --git a/src/engine/imap/Parameter.vala b/src/engine/imap/Parameter.vala index 3462e24d..d1361c7f 100644 --- a/src/engine/imap/Parameter.vala +++ b/src/engine/imap/Parameter.vala @@ -12,19 +12,65 @@ public abstract class Geary.Imap.Parameter : Object, Serializable { public abstract string to_string(); } +public class Geary.Imap.NilParameter : Geary.Imap.Parameter { + public const string VALUE = "NIL"; + + private static NilParameter? _instance = null; + + public static NilParameter instance { + get { + if (_instance == null) + _instance = new NilParameter(); + + return _instance; + } + } + + private NilParameter() { + } + + public static bool is_nil(string str) { + return String.ascii_equali(VALUE, str); + } + + public override async void serialize(Serializer ser) throws Error { + ser.push_nil(); + } + + public override string to_string() { + return VALUE; + } +} + public class Geary.Imap.StringParameter : Geary.Imap.Parameter { public string value { get; private set; } + public string? nullable_value { + get { + return String.is_empty(value) ? null : value; + } + } - public StringParameter(string value) requires (!is_empty_string(value)) { + public StringParameter(string value) requires (!String.is_empty(value)) { this.value = value; } - public StringParameter.NIL() { - this.value = "nil"; + public bool equals_cs(string value) { + return this.value == value; } - public bool is_nil() { - return value.down() == "nil"; + public bool equals_ci(string value) { + return this.value.down() == value.down(); + } + + // TODO: This does not check that the value is a properly-formed integer. This should be + // added later. + public int as_int() throws ImapError { + return int.parse(value); + } + + // TODO: This does not check that the value is a properly-formed long. + public long as_long() throws ImapError { + return long.parse(value); } public override string to_string() { @@ -99,19 +145,61 @@ public class Geary.Imap.ListParameter : Geary.Imap.Parameter { return list.get(index); } + public Parameter get_required(int index) throws ImapError { + Parameter? param = list.get(index); + if (param == null) + throw new ImapError.TYPE_ERROR("No parameter at index %d", index); + + return param; + } + public Parameter get_as(int index, Type type) throws ImapError { assert(type.is_a(typeof(Parameter))); - if (index >= list.size) - throw new ImapError.TYPE_ERROR("No parameter at index %d", index); - - Parameter param = list.get(index); + Parameter param = get_required(index); if (!param.get_type().is_a(type)) throw new ImapError.TYPE_ERROR("Parameter %d is not of type %s", index, type.name()); return param; } + public Parameter? get_as_nullable(int index, Type type) throws ImapError { + assert(type.is_a(typeof(Parameter))); + + Parameter param = get_required(index); + if (param is NilParameter) + return null; + + if (!param.get_type().is_a(type)) + throw new ImapError.TYPE_ERROR("Parameter %d is not of type %s", index, type.name()); + + return param; + } + + public StringParameter get_as_string(int index) throws ImapError { + return (StringParameter) get_as(index, typeof(StringParameter)); + } + + public StringParameter? get_as_nullable_string(int index) throws ImapError { + return (StringParameter?) get_as_nullable(index, typeof(StringParameter)); + } + + public ListParameter get_as_list(int index) throws ImapError { + return (ListParameter) get_as(index, typeof(ListParameter)); + } + + public ListParameter? get_as_nullable_list(int index) throws ImapError { + return (ListParameter?) get_as_nullable(index, typeof(ListParameter)); + } + + public LiteralParameter get_as_literal(int index) throws ImapError { + return (LiteralParameter) get_as(index, typeof(LiteralParameter)); + } + + public LiteralParameter? get_as_nullable_literal(int index) throws ImapError { + return (LiteralParameter?) get_as_nullable(index, typeof(LiteralParameter)); + } + public Gee.List get_all() { return list.read_only_view; } diff --git a/src/engine/imap/Serializer.vala b/src/engine/imap/Serializer.vala index ade00bdd..11e3ad02 100644 --- a/src/engine/imap/Serializer.vala +++ b/src/engine/imap/Serializer.vala @@ -42,6 +42,10 @@ public class Geary.Imap.Serializer { douts.put_byte(' ', null); } + public void push_nil() throws Error { + douts.put_string(NilParameter.VALUE, null); + } + public void push_eol() throws Error { douts.put_string("\r\n", null); } diff --git a/src/engine/imap/StatusResponse.vala b/src/engine/imap/StatusResponse.vala index 16f80416..86680fb6 100644 --- a/src/engine/imap/StatusResponse.vala +++ b/src/engine/imap/StatusResponse.vala @@ -45,7 +45,7 @@ public class Geary.Imap.StatusResponse : ServerResponse { start_index++; } - return !is_empty_string(builder.str) ? builder.str : null; + return !String.is_empty(builder.str) ? builder.str : null; } } diff --git a/src/engine/rfc822/MailboxAddress.vala b/src/engine/rfc822/MailboxAddress.vala new file mode 100644 index 00000000..15abecf3 --- /dev/null +++ b/src/engine/rfc822/MailboxAddress.vala @@ -0,0 +1,34 @@ +/* Copyright 2011 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +public class Geary.RFC822.MailboxAddress { + public string? name { get; private set; } + public string? source_route { get; private set; } + public string mailbox { get; private set; } + public string domain { get; private set; } + public string address { get; private set; } + + public MailboxAddress(string? name, string? source_route, string mailbox, string domain) { + this.name = name; + this.source_route = source_route; + this.mailbox = mailbox; + this.domain = domain; + this.address = "%s@%s".printf(mailbox, domain); + } + + /** + * Returns a human-readable formatted address, showing the name (if available) and the email + * address in angled brackets. + */ + public string get_full_address() { + return String.is_empty(name) ? "<%s>".printf(address) : "%s <%s>".printf(name, address); + } + + public string to_string() { + return get_full_address(); + } +} + diff --git a/src/engine/rfc822/MessageData.vala b/src/engine/rfc822/MessageData.vala new file mode 100644 index 00000000..fcdadfd1 --- /dev/null +++ b/src/engine/rfc822/MessageData.vala @@ -0,0 +1,84 @@ +/* Copyright 2011 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * RFC822.MessageData represents a base class for all the various elements that may be present in + * an RFC822 message header. Note that some common elements (such as MailAccount) are not + * MessageData because they exist in an RFC822 header in list (i.e. multiple email addresses) form. + */ + +public interface Geary.RFC822.MessageData : Geary.Common.MessageData { +} + +public class Geary.RFC822.MessageID : Geary.Common.StringMessageData, Geary.RFC822.MessageData { + public MessageID(string value) { + base (value); + } +} + +public class Geary.RFC822.Date : Geary.Common.StringMessageData, Geary.RFC822.MessageData { + public Date(string value) { + base (value); + } +} + +public class Geary.RFC822.Size : Geary.Common.LongMessageData, Geary.RFC822.MessageData { + public Size(long value) { + base (value); + } +} + +public class Geary.RFC822.Subject : Geary.Common.StringMessageData, Geary.RFC822.MessageData { + public Subject(string value) { + base (value); + } +} + +public class Geary.RFC822.MailboxAddresses : Geary.Common.MessageData, Geary.RFC822.MessageData { + private Gee.List addrs = new Gee.ArrayList(); + + public MailboxAddresses(Gee.Collection addrs) { + this.addrs.add_all(addrs); + } + + public int get_count() { + return addrs.size; + } + + public MailboxAddress? get(int index) { + return addrs.get(index); + } + + public Gee.List get_all() { + return addrs.read_only_view; + } + + public override string to_string() { + return addrs.size > 0 ? emit() : "(no addresses)"; + } + + public override string emit() { + switch (addrs.size) { + case 0: + return ""; + + case 1: + return addrs[0].to_string(); + + default: + StringBuilder builder = new StringBuilder(); + foreach (MailboxAddress addr in addrs) { + if (!String.is_empty(builder.str)) + builder.append(", "); + + builder.append(addr.to_string()); + } + + return builder.str; + } + } +} + diff --git a/src/engine/util/string.vala b/src/engine/util/string.vala index 9b0b7677..d2496b47 100644 --- a/src/engine/util/string.vala +++ b/src/engine/util/string.vala @@ -4,7 +4,39 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -public inline bool is_empty_string(string? str) { +namespace String { + +public inline bool is_empty(string? str) { return (str == null || str[0] == 0); } +public int ascii_cmp(string a, string b) { + return strcmp(a, b); +} + +public int ascii_cmpi(string a, string b) { + char *aptr = a; + char *bptr = b; + for (;;) { + int diff = *aptr - *bptr; + if (diff != 0) + return diff; + + if (*aptr == '\0') + return 0; + + aptr++; + bptr++; + } +} + +public inline bool ascii_equal(string a, string b) { + return ascii_cmp(a, b) == 0; +} + +public inline bool ascii_equali(string a, string b) { + return ascii_cmpi(a, b) == 0; +} + +} +