[geary] Move UID/UIDVALIDITY/SeqNum from signed 32 -> unsigned 32: Bug #737642



commit da2efeab03a33b77b4029145129b685b7b85188e
Author: Jim Nelson <jim yorba org>
Date:   Fri Nov 14 15:18:12 2014 -0800

    Move UID/UIDVALIDITY/SeqNum from signed 32 -> unsigned 32: Bug #737642
    
    Prior code inadvertantly limited UID and UIDVALIDITY to signed, not
    unsigned, 32-bit integers.  I've also added stronger checking of
    numbers received off the wire, both that they're truly numeric
    according to RFC and that they're within boundaries, if specified.
    
    This also enforces bit width of integers from IMAP string parameters
    to avoid similar problems in the future.

 src/engine/api/geary-email-properties.vala         |    4 +-
 src/engine/api/geary-email.vala                    |   12 +---
 src/engine/common/common-message-data.vala         |   26 ----------
 src/engine/imap-db/imap-db-folder.vala             |   14 +++---
 src/engine/imap-db/imap-db-message-row.vala        |    4 +-
 .../imap-engine/imap-engine-minimal-folder.vala    |   14 +++---
 .../imap-engine-abstract-list-email.vala           |   10 ++--
 src/engine/imap/api/imap-folder.vala               |   13 +++--
 src/engine/imap/command/imap-message-set.vala      |    2 +-
 src/engine/imap/message/imap-message-data.vala     |    2 +-
 src/engine/imap/message/imap-sequence-number.vala  |   52 ++++++++++++--------
 src/engine/imap/message/imap-uid-validity.vala     |   50 +++++++++++++++++--
 src/engine/imap/message/imap-uid.vala              |   43 +++++++++++++++--
 .../imap/parameter/imap-number-parameter.vala      |   19 +++++---
 .../imap/parameter/imap-string-parameter.vala      |   34 ++++++-------
 .../imap/response/imap-fetch-data-decoder.vala     |    4 +-
 src/engine/imap/response/imap-fetched-data.vala    |    3 +-
 src/engine/imap/response/imap-response-code.vala   |    6 +-
 src/engine/imap/response/imap-server-data.vala     |   16 +++---
 src/engine/imap/response/imap-status-data.vala     |   10 ++--
 src/engine/imap/transport/imap-client-session.vala |    2 +-
 src/engine/rfc822/rfc822-message-data.vala         |    4 +-
 22 files changed, 203 insertions(+), 141 deletions(-)
---
diff --git a/src/engine/api/geary-email-properties.vala b/src/engine/api/geary-email-properties.vala
index 41b5970..927d6f5 100644
--- a/src/engine/api/geary-email-properties.vala
+++ b/src/engine/api/geary-email-properties.vala
@@ -24,9 +24,9 @@ public abstract class Geary.EmailProperties : BaseObject {
     /**
      * Total size of the email (header and body) in bytes.
      */
-    public long total_bytes { get; protected set; }
+    public int64 total_bytes { get; protected set; }
     
-    public EmailProperties(DateTime date_received, long total_bytes) {
+    public EmailProperties(DateTime date_received, int64 total_bytes) {
         this.date_received = date_received;
         this.total_bytes = total_bytes;
     }
diff --git a/src/engine/api/geary-email.vala b/src/engine/api/geary-email.vala
index f7ffddc..6100252 100644
--- a/src/engine/api/geary-email.vala
+++ b/src/engine/api/geary-email.vala
@@ -397,15 +397,9 @@ public class Geary.Email : BaseObject {
         if (aprop == null || bprop == null)
             return compare_id_ascending(aemail, bemail);
         
-        long asize = aprop.total_bytes;
-        long bsize = bprop.total_bytes;
-        
-        if (asize < bsize)
-            return -1;
-        else if (asize > bsize)
-            return 1;
-        else
-            return compare_id_ascending(aemail, bemail);
+        int cmp = (int) (aprop.total_bytes - bprop.total_bytes).clamp(-1, 1);
+        
+        return (cmp != 0) ? cmp : compare_id_ascending(aemail, bemail);
     }
     
     /**
diff --git a/src/engine/common/common-message-data.vala b/src/engine/common/common-message-data.vala
index 59b39a2..65f420e 100644
--- a/src/engine/common/common-message-data.vala
+++ b/src/engine/common/common-message-data.vala
@@ -83,32 +83,6 @@ public abstract class Geary.MessageData.IntMessageData : AbstractMessageData,
     }
 }
 
-public abstract class Geary.MessageData.LongMessageData : AbstractMessageData,
-    Gee.Hashable<LongMessageData> {
-    public long value { get; private set; }
-    
-    private uint stored_hash = uint.MAX;
-    
-    public LongMessageData(long value) {
-        this.value = value;
-    }
-    
-    public virtual bool equal_to(LongMessageData other) {
-        if (this == other)
-            return true;
-        
-        return (value == other.value);
-    }
-    
-    public virtual uint hash() {
-        return (stored_hash != uint.MAX) ? stored_hash : (stored_hash = int64_hash((int64) value));
-    }
-    
-    public override string to_string() {
-        return value.to_string();
-    }
-}
-
 public abstract class Geary.MessageData.Int64MessageData : AbstractMessageData,
     Gee.Hashable<Int64MessageData> {
     public int64 value { get; private set; }
diff --git a/src/engine/imap-db/imap-db-folder.vala b/src/engine/imap-db/imap-db-folder.vala
index 07f2aaf..b9a4bae 100644
--- a/src/engine/imap-db/imap-db-folder.vala
+++ b/src/engine/imap-db/imap-db-folder.vala
@@ -601,7 +601,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
     }
     
     // pos is 1-based.  This method does not respect messages marked for removal.
-    public async ImapDB.EmailIdentifier? get_id_at_async(int pos, Cancellable? cancellable) throws Error {
+    public async ImapDB.EmailIdentifier? get_id_at_async(int64 pos, Cancellable? cancellable) throws Error {
         assert(pos >= 1);
         
         ImapDB.EmailIdentifier? id = null;
@@ -615,7 +615,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
                 OFFSET ?
             """);
             stmt.bind_rowid(0, folder_id);
-            stmt.bind_int(1, pos - 1);
+            stmt.bind_int64(1, pos - 1);
             
             Db.Result results = stmt.exec(cancellable);
             if (!results.finished)
@@ -1180,7 +1180,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         Imap.EmailProperties? imap_properties = (Imap.EmailProperties) email.properties;
         string? internaldate = (imap_properties != null && imap_properties.internaldate != null)
             ? imap_properties.internaldate.serialize() : null;
-        long rfc822_size = (imap_properties != null) ? imap_properties.rfc822_size.value : -1;
+        int64 rfc822_size = (imap_properties != null) ? imap_properties.rfc822_size.value : -1;
         
         if (String.is_empty(internaldate) || rfc822_size < 0) {
             debug("Unable to detect duplicates for %s (%s available but invalid)", email.id.to_string(),
@@ -1202,8 +1202,8 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         
         int64 message_id = results.rowid_at(0);
         if (results.next(cancellable)) {
-            debug("Warning: multiple messages with the same internaldate (%s) and size (%lu) in %s",
-                internaldate, rfc822_size, to_string());
+            debug("Warning: multiple messages with the same internaldate (%s) and size (%s) in %s",
+                internaldate, rfc822_size.to_string(), to_string());
         }
         
         Db.Statement search_stmt = cx.prepare(
@@ -1323,7 +1323,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         stmt.bind_string(16, row.email_flags);
         stmt.bind_string(17, row.internaldate);
         stmt.bind_int64(18, row.internaldate_time_t);
-        stmt.bind_long(19, row.rfc822_size);
+        stmt.bind_int64(19, row.rfc822_size);
         
         int64 message_id = stmt.exec_insert(cancellable);
         
@@ -1732,7 +1732,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
                 "UPDATE MessageTable SET internaldate=?, internaldate_time_t=?, rfc822_size=? WHERE id=?");
             stmt.bind_string(0, row.internaldate);
             stmt.bind_int64(1, row.internaldate_time_t);
-            stmt.bind_long(2, row.rfc822_size);
+            stmt.bind_int64(2, row.rfc822_size);
             stmt.bind_rowid(3, row.id);
             
             stmt.exec(cancellable);
diff --git a/src/engine/imap-db/imap-db-message-row.vala b/src/engine/imap-db/imap-db-message-row.vala
index 98c5c60..a2c4c9a 100644
--- a/src/engine/imap-db/imap-db-message-row.vala
+++ b/src/engine/imap-db/imap-db-message-row.vala
@@ -34,7 +34,7 @@ private class Geary.ImapDB.MessageRow {
     public string? email_flags { get; set; default = null; }
     public string? internaldate { get; set; default = null; }
     public time_t internaldate_time_t { get; set; default = -1; }
-    public long rfc822_size { get; set; default = -1; }
+    public int64 rfc822_size { get; set; default = -1; }
     
     public MessageRow() {
     }
@@ -93,7 +93,7 @@ private class Geary.ImapDB.MessageRow {
         if (fields.is_all_set(Geary.Email.Field.PROPERTIES)) {
             internaldate = results.string_for("internaldate");
             internaldate_time_t = (time_t) results.int64_for("internaldate_time_t");
-            rfc822_size = results.long_for("rfc822_size");
+            rfc822_size = results.int64_for("rfc822_size");
         }
     }
     
diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala 
b/src/engine/imap-engine/imap-engine-minimal-folder.vala
index af8750f..45e49f0 100644
--- a/src/engine/imap-engine/imap-engine-minimal-folder.vala
+++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala
@@ -1017,8 +1017,8 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde
     
     // This MUST only be called from ReplayRemoval.
     internal async void do_replay_removed_message(int reported_remote_count, Imap.SequenceNumber 
remote_position) {
-        debug("%s do_replay_removed_message: current remote_count=%d remote_position=%d 
reported_remote_count=%d",
-            to_string(), remote_count, remote_position.value, reported_remote_count);
+        debug("%s do_replay_removed_message: current remote_count=%d remote_position=%s 
reported_remote_count=%d",
+            to_string(), remote_count, remote_position.value.to_string(), reported_remote_count);
         
         if (!remote_position.is_valid()) {
             debug("%s do_replay_removed_message: ignoring, invalid remote position or count",
@@ -1028,7 +1028,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde
         }
         
         int local_count = -1;
-        int local_position = -1;
+        int64 local_position = -1;
         
         ImapDB.EmailIdentifier? owned_id = null;
         try {
@@ -1041,13 +1041,13 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde
             // zero or negative means the message exists beyond the local vector's range, so
             // nothing to do there
             if (local_position > 0) {
-                debug("%s do_replay_removed_message: local_count=%d local_position=%d", to_string(),
-                    local_count, local_position);
+                debug("%s do_replay_removed_message: local_count=%d local_position=%s", to_string(),
+                    local_count, local_position.to_string());
                 
                 owned_id = yield local_folder.get_id_at_async(local_position, null);
             } else {
-                debug("%s do_replay_removed_message: message not stored locally (local_count=%d 
local_position=%d)",
-                    to_string(), local_count, local_position);
+                debug("%s do_replay_removed_message: message not stored locally (local_count=%d 
local_position=%s)",
+                    to_string(), local_count, local_position.to_string());
             }
         } catch (Error err) {
             debug("%s do_replay_removed_message: unable to determine ID of removed message %s: %s",
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
index 4f1a3e2..e9de104 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
@@ -219,7 +219,7 @@ private abstract class Geary.ImapEngine.AbstractListEmail : Geary.ImapEngine.Sen
         if (flags.is_oldest_to_newest()) {
             if (initial_uid == null) {
                 // if oldest to newest and initial-id is null, then start at the bottom
-                low_pos = new Imap.SequenceNumber(1);
+                low_pos = new Imap.SequenceNumber(Imap.SequenceNumber.MIN);
             } else {
                 Gee.Map<Imap.UID, Imap.SequenceNumber>? map = yield 
owner.remote_folder.uid_to_position_async(
                     new Imap.MessageSet.uid(initial_uid), cancellable);
@@ -252,7 +252,7 @@ private abstract class Geary.ImapEngine.AbstractListEmail : Geary.ImapEngine.Sen
                 // server to determine the position number of a particular UID, this makes sense
                 assert(high_pos != null);
                 low_pos = new Imap.SequenceNumber(
-                    Numeric.int_floor((high_pos.value - count) + 1, 1));
+                    Numeric.int64_floor((high_pos.value - count) + 1, 1));
             }
         }
         
@@ -267,7 +267,7 @@ private abstract class Geary.ImapEngine.AbstractListEmail : Geary.ImapEngine.Sen
         }
         
         Imap.MessageSet msg_set;
-        int actual_count = -1;
+        int64 actual_count = -1;
         if (high_pos != null) {
             msg_set = new Imap.MessageSet.range_by_first_last(low_pos, high_pos);
             actual_count = (high_pos.value - low_pos.value) + 1;
@@ -275,9 +275,9 @@ private abstract class Geary.ImapEngine.AbstractListEmail : Geary.ImapEngine.Sen
             msg_set = new Imap.MessageSet.range_to_highest(low_pos);
         }
         
-        debug("%s: Performing vector expansion using %s for initial_uid=%s count=%d actual_count=%d 
local_count=%d remote_count=%d oldest_to_newest=%s",
+        debug("%s: Performing vector expansion using %s for initial_uid=%s count=%d actual_count=%s 
local_count=%d remote_count=%d oldest_to_newest=%s",
             owner.to_string(), msg_set.to_string(),
-            (initial_uid != null) ? initial_uid.to_string() : "(null)", count, actual_count,
+            (initial_uid != null) ? initial_uid.to_string() : "(null)", count, actual_count.to_string(),
             local_count, remote_count, flags.is_oldest_to_newest().to_string());
         
         Gee.List<Geary.Email>? list = yield owner.remote_folder.list_email_async(msg_set,
diff --git a/src/engine/imap/api/imap-folder.vala b/src/engine/imap/api/imap-folder.vala
index 81aed1d..82f316a 100644
--- a/src/engine/imap/api/imap-folder.vala
+++ b/src/engine/imap/api/imap-folder.vala
@@ -235,11 +235,16 @@ private class Geary.Imap.Folder : BaseObject {
             recent(total);
     }
     
-    private void on_search(Gee.List<int> seq_or_uid) {
+    private void on_search(Gee.List<int64?> seq_or_uid) {
         // All SEARCH from this class are UID SEARCH, so can reliably convert and add to
         // accumulator
-        foreach (int uid in seq_or_uid)
-            search_accumulator.add(new UID(uid));
+        foreach (int64 uid in seq_or_uid) {
+            try {
+                search_accumulator.add(new UID.checked(uid));
+            } catch (ImapError imaperr) {
+                debug("%s Unable to process SEARCH UID result: %s", to_string(), imaperr.message);
+            }
+        }
     }
     
     private void on_status_response(StatusResponse status_response) {
@@ -1002,7 +1007,7 @@ private class Geary.Imap.Folder : BaseObject {
         StatusResponse response = responses.get(cmd);
         if (response.status == Status.OK && response.response_code != null &&
             response.response_code.get_response_code_type().is_value("appenduid")) {
-            UID new_id = new UID(response.response_code.get_as_string(2).as_int());
+            UID new_id = new UID.checked(response.response_code.get_as_string(2).as_int64());
             
             return new ImapDB.EmailIdentifier.no_message_id(new_id);
         }
diff --git a/src/engine/imap/command/imap-message-set.vala b/src/engine/imap/command/imap-message-set.vala
index 21497e2..85af92a 100644
--- a/src/engine/imap/command/imap-message-set.vala
+++ b/src/engine/imap/command/imap-message-set.vala
@@ -47,7 +47,7 @@ public class Geary.Imap.MessageSet : BaseObject {
         assert(count > 0);
         
         value = (count > 1)
-            ? "%d:%d".printf(low_seq_num.value, low_seq_num.value + count - 1)
+            ? "%s:%s".printf(low_seq_num.value.to_string(), (low_seq_num.value + count - 1).to_string())
             : low_seq_num.serialize();
     }
     
diff --git a/src/engine/imap/message/imap-message-data.vala b/src/engine/imap/message/imap-message-data.vala
index 7e18aaa..e0a5dd3 100644
--- a/src/engine/imap/message/imap-message-data.vala
+++ b/src/engine/imap/message/imap-message-data.vala
@@ -20,7 +20,7 @@ public interface Geary.Imap.MessageData : Geary.MessageData.AbstractMessageData
 }
 
 public class Geary.Imap.RFC822Size : Geary.RFC822.Size, Geary.Imap.MessageData {
-    public RFC822Size(long value) {
+    public RFC822Size(int64 value) {
         base (value);
     }
 }
diff --git a/src/engine/imap/message/imap-sequence-number.vala 
b/src/engine/imap/message/imap-sequence-number.vala
index c71b7a8..80bec5c 100644
--- a/src/engine/imap/message/imap-sequence-number.vala
+++ b/src/engine/imap/message/imap-sequence-number.vala
@@ -12,48 +12,58 @@
  * @see UID
  */
 
-public class Geary.Imap.SequenceNumber : Geary.MessageData.IntMessageData, Geary.Imap.MessageData,
+public class Geary.Imap.SequenceNumber : Geary.MessageData.Int64MessageData, Geary.Imap.MessageData,
     Gee.Comparable<SequenceNumber> {
     /**
      * Minimum value of a valid { link SequenceNumber}.
      *
      * See [[http://tools.ietf.org/html/rfc3501#section-2.3.1.2]]
      */
-    public int MIN_VALUE = 1;
+    public const int64 MIN = 1;
+    
+    /**
+     * Upper limit of a valid { link SequenceNumber}.
+     */
+    public const int64 MAX = 0xFFFFFFFF;
     
     /**
      * Create a new { link SequenceNumber}.
      *
-     * This does not check if the value is valid, i.e. >= { link MIN_VALUE}.
+     * This does not check if the value is valid.
+     *
+     * @see is_value_valid
+     * @see SequenceNumber.checked
      */
-    public SequenceNumber(int value) {
+    public SequenceNumber(int64 value) {
         base (value);
     }
     
     /**
-     * Converts an array of ints into an array of { link SequenceNumber}s.
+     * Create a new { link SequenceNumber}, throwing { link ImapError.INVALID} if an invalid value
+     * is passed in.
+     *
+     * @see is_value_valid
+     * @see SequenceNumber
      */
-    public static SequenceNumber[] to_list(int[] value_array) {
-        SequenceNumber[] list = new SequenceNumber[0];
-        foreach (int value in value_array)
-            list += new SequenceNumber(value);
+    public SequenceNumber.checked(int64 value) throws ImapError {
+        if (!is_value_valid(value))
+            throw new ImapError.INVALID("Invalid sequence number %s", value.to_string());
         
-        return list;
+        base (value);
     }
     
     /**
-     * Defined as { link value} >= { link MIN_VALUE}.
+     * Defined as { link value} >= { link MIN} and <= { link MAX}.
      */
-    public bool is_valid() {
-        return value >= MIN_VALUE;
+    public static bool is_value_valid(int64 value) {
+        return value >= MIN && value <= MAX;
     }
     
     /**
-     * Returns a new { link SequenceNumber} that is one higher than this value.
-     *
+     * Defined as { link value} >= { link MIN} and <= { link MAX}.
      */
-    public SequenceNumber inc() {
-        return new SequenceNumber(value + 1);
+    public bool is_valid() {
+        return is_value_valid(value);
     }
     
     /**
@@ -62,16 +72,16 @@ public class Geary.Imap.SequenceNumber : Geary.MessageData.IntMessageData, Geary
      * Returns null if the decremented value is less than { link MIN_VALUE}.
      */
     public SequenceNumber? dec() {
-        return (value > MIN_VALUE) ? new SequenceNumber(value - 1) : null;
+        return (value > MIN) ? new SequenceNumber(value - 1) : null;
     }
     
     /**
      * Returns a new { link SequenceNumber} that is one lower than this value.
      *
-     * Returns a SequenceNumber of MIN_VALUE if the decremented value is less than it.
+     * Returns a SequenceNumber of MIN if the decremented value is less than it.
      */
     public SequenceNumber dec_clamped() {
-        return (value > MIN_VALUE) ? new SequenceNumber(value - 1) : new SequenceNumber(MIN_VALUE);
+        return (value > MIN) ? new SequenceNumber(value - 1) : new SequenceNumber(MIN);
     }
     
     /**
@@ -100,7 +110,7 @@ public class Geary.Imap.SequenceNumber : Geary.MessageData.IntMessageData, Geary
     }
     
     public virtual int compare_to(SequenceNumber other) {
-        return value - other.value;
+        return (int) (value - other.value).clamp(-1, 1);
     }
     
     public string serialize() {
diff --git a/src/engine/imap/message/imap-uid-validity.vala b/src/engine/imap/message/imap-uid-validity.vala
index fe6875a..6dd779a 100644
--- a/src/engine/imap/message/imap-uid-validity.vala
+++ b/src/engine/imap/message/imap-uid-validity.vala
@@ -13,13 +13,55 @@
  */
 
 public class Geary.Imap.UIDValidity : Geary.MessageData.Int64MessageData, Geary.Imap.MessageData {
-    // Using statics because int32.MAX is static, not const (??)
-    public static int64 MIN = 1;
-    public static int64 MAX = int32.MAX;
-    public static int64 INVALID = -1;
+    /**
+     * Minimum valid value for a { link UIDValidity}.
+     */
+    public const int64 MIN = 1;
     
+    /**
+     * Maximum valid value for a { link UIDValidity}.
+     */
+    public const int64 MAX = 0xFFFFFFFF;
+    
+    /**
+     * Invalid (placeholder) { link UIDValidity} value.
+     */
+    public const int64 INVALID = -1;
+    
+    /**
+     * Creates a new { link UIDValidity} without checking for valid values.
+     *
+     * @see UIDValidity.checked
+     */
     public UIDValidity(int64 value) {
         base (value);
     }
+    
+    /**
+     * Creates a new { link UIDValidity}, throwing { link ImapError.INVALID} if the supplied value
+     * is invalid.
+     *
+     * @see is_value_valid
+     */
+    public UIDValidity.checked(int64 value) throws ImapError {
+        if (!is_value_valid(value))
+            throw new ImapError.INVALID("Invalid UIDVALIDITY %s", value.to_string());
+        
+        base (value);
+    }
+    
+    /**
+     * @see is_value_valid
+     */
+    public bool is_valid() {
+        return is_value_valid(value);
+    }
+    
+    /**
+     * Returns true if the supplied value is between { link MIN} and { link MAX}, inclusive.
+     */
+    public static bool is_value_valid(int64 val) {
+        return Numeric.int64_in_range_inclusive(val, MIN, MAX);
+    }
 }
 
diff --git a/src/engine/imap/message/imap-uid.vala b/src/engine/imap/message/imap-uid.vala
index 1cd57bf..f4bfec9 100644
--- a/src/engine/imap/message/imap-uid.vala
+++ b/src/engine/imap/message/imap-uid.vala
@@ -14,19 +14,54 @@
 
 public class Geary.Imap.UID : Geary.MessageData.Int64MessageData, Geary.Imap.MessageData,
     Gee.Comparable<Geary.Imap.UID> {
-    // Using statics because int32.MAX is static, not const (??)
-    public static int64 MIN = 1;
-    public static int64 MAX = int32.MAX;
-    public static int64 INVALID = -1;
+    /**
+     * Minimum valid value for a { link UID}.
+     */
+    public const int64 MIN = 1;
+    
+    /**
+     * Maximum valid value for a { link UID}.
+     */
+    public const int64 MAX = 0xFFFFFFFF;
     
+    /**
+     * Invalid (placeholder) { link UID} value.
+     */
+    public const int64 INVALID = -1;
+    
+    /**
+     * Creates a new { link UID} without checking for validity.
+     *
+     * @see UID.checked
+     * @see is_value_valid
+     */
     public UID(int64 value) {
         base (value);
     }
     
+    /**
+     * Creates a new { link UID}, throwing an { link ImapError.INVALID} if the supplied value is
+     * not a positive unsigned 32-bit integer.
+     *
+     * @see is_value_valid
+     */
+    public UID.checked(int64 value) throws ImapError {
+        if (!is_value_valid(value))
+            throw new ImapError.INVALID("Invalid UID %s", value.to_string());
+        
+        base (value);
+    }
+    
+    /**
+     * @see is_value_valid
+     */
     public bool is_valid() {
         return is_value_valid(value);
     }
     
+    /**
+     * Returns true if the supplied value is between { link MIN} and { link MAX}, inclusive.
+     */
     public static bool is_value_valid(int64 val) {
         return Numeric.int64_in_range_inclusive(val, MIN, MAX);
     }
diff --git a/src/engine/imap/parameter/imap-number-parameter.vala 
b/src/engine/imap/parameter/imap-number-parameter.vala
index f6c29f5..e32c3cc 100644
--- a/src/engine/imap/parameter/imap-number-parameter.vala
+++ b/src/engine/imap/parameter/imap-number-parameter.vala
@@ -53,37 +53,42 @@ public class Geary.Imap.NumberParameter : UnquotedStringParameter {
      * positive value.  is_negative returns set to true if that's the case.  is_negative is only
      * a valid value if the method returns true itself.
      *
+     * is_negative is false for zero ("0") and negative zero ("-0").
+     *
      * Empty strings (null or zero-length) are considered non-numeric.  Leading and trailing
      * whitespace are stripped before evaluating the string.
      */
-    public static bool is_numeric(string s, out bool is_negative) {
+    public static bool is_ascii_numeric(string ascii, out bool is_negative) {
         is_negative = false;
         
-        string str = s.strip();
+        string str = ascii.strip();
         
         if (String.is_empty(str))
             return false;
         
-        bool first_char = true;
+        bool has_nonzero = false;
         int index = 0;
         for (;;) {
             char ch = str[index++];
             if (ch == String.EOS)
                 break;
             
-            if (first_char && ch == '-') {
+            if (index == 1 && ch == '-') {
                 is_negative = true;
-                first_char = false;
                 
                 continue;
             }
             
-            first_char = false;
-            
             if (!ch.isdigit())
                 return false;
+            
+            if (ch != '0')
+                has_nonzero = true;
         }
         
+        if (is_negative && !has_nonzero)
+            is_negative = false;
+        
         return true;
     }
 }
diff --git a/src/engine/imap/parameter/imap-string-parameter.vala 
b/src/engine/imap/parameter/imap-string-parameter.vala
index 0a164f5..01b6f0b 100644
--- a/src/engine/imap/parameter/imap-string-parameter.vala
+++ b/src/engine/imap/parameter/imap-string-parameter.vala
@@ -52,7 +52,7 @@ public abstract class Geary.Imap.StringParameter : Geary.Imap.Parameter {
      * @see Parameter.get_for_string
      */
     public static StringParameter get_best_for(string value) throws ImapError {
-        if (NumberParameter.is_numeric(value, null))
+        if (NumberParameter.is_ascii_numeric(value, null))
             return new NumberParameter.from_ascii(value);
         
         switch (DataFormat.is_quoting_required(value)) {
@@ -164,32 +164,28 @@ public abstract class Geary.Imap.StringParameter : Geary.Imap.Parameter {
     }
     
     /**
-     * Converts the { link value} to an int, clamped between clamp_min and clamp_max.
+     * Converts the { link value} to a signed 32-bit integer, clamped between clamp_min and clamp_max.
      *
-     * TODO: This does not check that the value is a properly-formed integer.  This should be
-     *. added later.
+     * ImapError.INVALID is thrown if the { link StringParameter} contains non-numeric values.  No
+     * error is thrown if the numeric value is outside the clamped range.
      */
-    public int as_int(int clamp_min = int.MIN, int clamp_max = int.MAX) throws ImapError {
-        return int.parse(ascii).clamp(clamp_min, clamp_max);
-    }
-    
-    /**
-     * Converts the { link value} to a long integer, clamped between clamp_min and clamp_max.
-     *
-     * TODO: This does not check that the value is a properly-formed long integer.  This should be
-     *. added later.
-     */
-    public long as_long(int clamp_min = int.MIN, int clamp_max = int.MAX) throws ImapError {
-        return long.parse(ascii).clamp(clamp_min, clamp_max);
+    public int32 as_int32(int32 clamp_min = int32.MIN, int32 clamp_max = int32.MAX) throws ImapError {
+        if (!NumberParameter.is_ascii_numeric(ascii, null))
+            throw new ImapError.INVALID("Cannot convert \"%s\" to int32: not numeric", ascii);
+        
+        return (int32) int64.parse(ascii).clamp(clamp_min, clamp_max);
     }
     
     /**
-     * Converts the { link value} to a 64-bit integer, clamped between clamp_min and clamp_max.
+     * Converts the { link value} to a signed 64-bit integer, clamped between clamp_min and clamp_max.
      *
-     * TODO: This does not check that the value is a properly-formed 64-bit integer.  This should be
-     *. added later.
+     * ImapError.INVALID is thrown if the { link StringParameter} contains non-numeric values.  No
+     * error is thrown if the numeric value is outside the clamped range.
      */
     public int64 as_int64(int64 clamp_min = int64.MIN, int64 clamp_max = int64.MAX) throws ImapError {
+        if (!NumberParameter.is_ascii_numeric(ascii, null))
+            throw new ImapError.INVALID("Cannot convert \"%s\" to int64: not numeric", ascii);
+        
         return int64.parse(ascii).clamp(clamp_min, clamp_max);
     }
 }
diff --git a/src/engine/imap/response/imap-fetch-data-decoder.vala 
b/src/engine/imap/response/imap-fetch-data-decoder.vala
index 142a2a3..b4d5667 100644
--- a/src/engine/imap/response/imap-fetch-data-decoder.vala
+++ b/src/engine/imap/response/imap-fetch-data-decoder.vala
@@ -85,7 +85,7 @@ public class Geary.Imap.UIDDecoder : Geary.Imap.FetchDataDecoder {
     }
     
     protected override MessageData decode_string(StringParameter stringp) throws ImapError {
-        return new UID(stringp.as_int());
+        return new UID.checked(stringp.as_int64());
     }
 }
 
@@ -119,7 +119,7 @@ public class Geary.Imap.RFC822SizeDecoder : Geary.Imap.FetchDataDecoder {
     }
     
     protected override MessageData decode_string(StringParameter stringp) throws ImapError {
-        return new RFC822Size(stringp.as_long());
+        return new RFC822Size(stringp.as_int64(0, int64.MAX));
     }
 }
 
diff --git a/src/engine/imap/response/imap-fetched-data.vala b/src/engine/imap/response/imap-fetched-data.vala
index a759b85..54292d4 100644
--- a/src/engine/imap/response/imap-fetched-data.vala
+++ b/src/engine/imap/response/imap-fetched-data.vala
@@ -49,7 +49,8 @@ public class Geary.Imap.FetchedData : Object {
         if (!server_data.get_as_string(2).equals_ci(FetchCommand.NAME))
             throw new ImapError.PARSE_ERROR("Not FETCH data: %s", server_data.to_string());
         
-        FetchedData fetched_data = new FetchedData(new 
SequenceNumber(server_data.get_as_string(1).as_int()));
+        FetchedData fetched_data = new FetchedData(
+            new SequenceNumber.checked(server_data.get_as_string(1).as_int64()));
         
         // walk the list for each returned fetch data item, which is paired by its data item name
         // and the structured data itself
diff --git a/src/engine/imap/response/imap-response-code.vala 
b/src/engine/imap/response/imap-response-code.vala
index f76224e..fbd8ce8 100644
--- a/src/engine/imap/response/imap-response-code.vala
+++ b/src/engine/imap/response/imap-response-code.vala
@@ -27,7 +27,7 @@ public class Geary.Imap.ResponseCode : Geary.Imap.ListParameter {
         if (!get_response_code_type().is_value(ResponseCodeType.UIDNEXT))
             throw new ImapError.INVALID("Not UIDNEXT: %s", to_string());
         
-        return new UID(get_as_string(1).as_int());
+        return new UID.checked(get_as_string(1).as_int64());
     }
     
     /**
@@ -39,7 +39,7 @@ public class Geary.Imap.ResponseCode : Geary.Imap.ListParameter {
         if (!get_response_code_type().is_value(ResponseCodeType.UIDVALIDITY))
             throw new ImapError.INVALID("Not UIDVALIDITY: %s", to_string());
         
-        return new UIDValidity(get_as_string(1).as_int());
+        return new UIDValidity.checked(get_as_string(1).as_int64());
     }
     
     /**
@@ -51,7 +51,7 @@ public class Geary.Imap.ResponseCode : Geary.Imap.ListParameter {
         if (!get_response_code_type().is_value(ResponseCodeType.UNSEEN))
             throw new ImapError.INVALID("Not UNSEEN: %s", to_string());
         
-        return get_as_string(1).as_int(0, int.MAX);
+        return get_as_string(1).as_int32(0, int.MAX);
     }
     
     /**
diff --git a/src/engine/imap/response/imap-server-data.vala b/src/engine/imap/response/imap-server-data.vala
index 81c3ef0..f4d6101 100644
--- a/src/engine/imap/response/imap-server-data.vala
+++ b/src/engine/imap/response/imap-server-data.vala
@@ -79,7 +79,7 @@ public class Geary.Imap.ServerData : ServerResponse {
         if (server_data_type != ServerDataType.EXISTS)
             throw new ImapError.INVALID("Not EXISTS data: %s", to_string());
         
-        return get_as_string(1).as_int(0);
+        return get_as_string(1).as_int32(0);
     }
     
     /**
@@ -91,7 +91,7 @@ public class Geary.Imap.ServerData : ServerResponse {
         if (server_data_type != ServerDataType.EXPUNGE)
             throw new ImapError.INVALID("Not EXPUNGE data: %s", to_string());
         
-        return new SequenceNumber(get_as_string(1).as_int());
+        return new SequenceNumber.checked(get_as_string(1).as_int64());
     }
     
     /**
@@ -139,24 +139,24 @@ public class Geary.Imap.ServerData : ServerResponse {
         if (server_data_type != ServerDataType.RECENT)
             throw new ImapError.INVALID("Not RECENT data: %s", to_string());
         
-        return get_as_string(1).as_int(0);
+        return get_as_string(1).as_int32(0);
     }
     
     /**
-     * Parses the { link ServerData} into a { link ServerDataType.RECENT} value, if possible.
+     * Parses the { link ServerData} into a { link ServerDataType.SEARCH} value, if possible.
      *
-     * @throws ImapError.INVALID if not a { link ServerDataType.RECENT} value.
+     * @throws ImapError.INVALID if not a { link ServerDataType.SEARCH} value.
      */
-    public Gee.List<int> get_search() throws ImapError {
+    public Gee.List<int64?> get_search() throws ImapError {
         if (server_data_type != ServerDataType.SEARCH)
             throw new ImapError.INVALID("Not SEARCH data: %s", to_string());
         
-        Gee.List<int> results = new Gee.ArrayList<int>();
+        Gee.List<int64?> results = new Gee.ArrayList<int64?>();
         for (int ctr = 2; ctr < size; ctr++) {
             // can't directly return the result from as_int() into results as a Vala bug causes a
             // build policy violation for uncast int -> pointer on 64-bit architectures:
             // https://bugzilla.gnome.org/show_bug.cgi?id=720437
-            int result = get_as_string(ctr).as_int(0);
+            int64 result = get_as_string(ctr).as_int64(0);
             results.add(result);
         }
         
diff --git a/src/engine/imap/response/imap-status-data.vala b/src/engine/imap/response/imap-status-data.vala
index 56724c2..ea8d353 100644
--- a/src/engine/imap/response/imap-status-data.vala
+++ b/src/engine/imap/response/imap-status-data.vala
@@ -93,25 +93,25 @@ public class Geary.Imap.StatusData : Object {
                 switch (StatusDataType.from_parameter(typep)) {
                     case StatusDataType.MESSAGES:
                         // see note at UNSET
-                        messages = valuep.as_int(-1, int.MAX);
+                        messages = valuep.as_int32(-1, int.MAX);
                     break;
                     
                     case StatusDataType.RECENT:
                         // see note at UNSET
-                        recent = valuep.as_int(-1, int.MAX);
+                        recent = valuep.as_int32(-1, int.MAX);
                     break;
                     
                     case StatusDataType.UIDNEXT:
-                        uid_next = new UID(valuep.as_int());
+                        uid_next = new UID.checked(valuep.as_int64());
                     break;
                     
                     case StatusDataType.UIDVALIDITY:
-                        uid_validity = new UIDValidity(valuep.as_int());
+                        uid_validity = new UIDValidity.checked(valuep.as_int64());
                     break;
                     
                     case StatusDataType.UNSEEN:
                         // see note at UNSET
-                        unseen = valuep.as_int(-1, int.MAX);
+                        unseen = valuep.as_int32(-1, int.MAX);
                     break;
                     
                     default:
diff --git a/src/engine/imap/transport/imap-client-session.vala 
b/src/engine/imap/transport/imap-client-session.vala
index c157588..e1549c4 100644
--- a/src/engine/imap/transport/imap-client-session.vala
+++ b/src/engine/imap/transport/imap-client-session.vala
@@ -221,7 +221,7 @@ public class Geary.Imap.ClientSession : BaseObject {
     
     public signal void recent(int count);
     
-    public signal void search(Gee.List<int> seq_or_uid);
+    public signal void search(Gee.List<int64?> seq_or_uid);
     
     public signal void status(StatusData status_data);
     
diff --git a/src/engine/rfc822/rfc822-message-data.vala b/src/engine/rfc822/rfc822-message-data.vala
index 59def0f..e8ed1e3 100644
--- a/src/engine/rfc822/rfc822-message-data.vala
+++ b/src/engine/rfc822/rfc822-message-data.vala
@@ -210,8 +210,8 @@ public class Geary.RFC822.Date : Geary.RFC822.MessageData, Geary.MessageData.Abs
     }
 }
 
-public class Geary.RFC822.Size : Geary.MessageData.LongMessageData, Geary.RFC822.MessageData {
-    public Size(long value) {
+public class Geary.RFC822.Size : Geary.MessageData.Int64MessageData, Geary.RFC822.MessageData {
+    public Size(int64 value) {
         base (value);
     }
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]