github.com/jordwest/imap-server@v0.0.0-20200627020849-1cf758ba359f/types/sequence_numbers.go (about)

     1  package types
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strconv"
     7  	"strings"
     8  )
     9  
    10  // SequenceNumber represents a  single message identifier. Could be UID or
    11  // sequence number.
    12  // See RFC3501 section 9.
    13  type SequenceNumber string
    14  
    15  // Last returns true if this sequence number indicates the *last* sequence
    16  // number or UID available in this mailbox.
    17  // If false, this sequence number contains an integer value.
    18  func (s SequenceNumber) Last() bool {
    19  	if s == "*" {
    20  		return true
    21  	}
    22  	return false
    23  }
    24  
    25  // Nil returns true if no sequence number was specified.
    26  func (s SequenceNumber) Nil() bool {
    27  	if s == "" {
    28  		return true
    29  	}
    30  	return false
    31  }
    32  
    33  // IsValue returns true if the sequence number is a numeral value and not nil
    34  // or *.
    35  func (s SequenceNumber) IsValue() bool {
    36  	return (!s.Nil() && !s.Last())
    37  }
    38  
    39  // Value returns the integer value of the sequence number, if any is set.
    40  // If Nil or Last is true (ie, this sequence number is not an integer value)
    41  // then this returns 0 and an error.
    42  func (s SequenceNumber) Value() (uint32, error) {
    43  	if s.Last() {
    44  		return 0, fmt.Errorf("This sequence number indicates the last number in the mailbox and does not contain a value")
    45  	}
    46  	if s.Nil() {
    47  		return 0, fmt.Errorf("This sequence number is not set")
    48  	}
    49  
    50  	intVal, err := strconv.ParseUint(string(s), 10, 32)
    51  	if err != nil {
    52  		return 0, fmt.Errorf("Could not parse integer value of sequence number")
    53  	}
    54  	return uint32(intVal), nil
    55  }
    56  
    57  // SequenceRange represents a range of identifiers. eg in IMAP: 5:9 or 15:*
    58  type SequenceRange struct {
    59  	Min SequenceNumber
    60  	Max SequenceNumber
    61  }
    62  
    63  // SequenceSet represents set of sequence ranges. eg in IMAP: 1,3,5:9,18:*
    64  type SequenceSet []SequenceRange
    65  
    66  type errInvalidRangeString string
    67  type errInvalidSequenceSetString string
    68  
    69  func (e errInvalidRangeString) Error() string {
    70  	return fmt.Sprintf("Invalid sequence range string '%s' specified", string(e))
    71  }
    72  func (e errInvalidSequenceSetString) Error() string {
    73  	return fmt.Sprintf("Invalid sequence set string '%s' specified", string(e))
    74  }
    75  
    76  var rangeRegexp *regexp.Regexp
    77  var setRegexp *regexp.Regexp
    78  
    79  func init() {
    80  	// Regex for finding a sequence range
    81  	rangeRegexp = regexp.MustCompile("^(\\d{1,10}|\\*)" + // Range lower bound - digit or star
    82  		"(?:\\:(\\d{1,10}|\\*))?$") // Range upper bound - digit or star
    83  
    84  	// Regex for finding a sequence-set - ie, multiple sequence ranges
    85  	setRegexp = regexp.MustCompile("^((?:\\d{1,10}|\\*)(?:\\:(?:\\d{1,10}|\\*))?)" + // First range
    86  		"(?:" + // Match zero or more additional ranges
    87  		"," + // Must be separated by a comma
    88  		"((?:\\d{1,10}|\\*)(?:\\:(?:\\d{1,10}|\\*))?)" + // Additional ranges
    89  		")*" + // Match zero or more
    90  		"$")
    91  }
    92  
    93  // InterpretMessageRange creates a SequenceRange from the given string in the
    94  // IMAP format.
    95  func InterpretMessageRange(imapMessageRange string) (seqRange SequenceRange, err error) {
    96  	result := rangeRegexp.FindStringSubmatch(imapMessageRange)
    97  	if len(result) == 0 {
    98  		return SequenceRange{}, errInvalidRangeString(imapMessageRange)
    99  	}
   100  
   101  	first := SequenceNumber(result[1])
   102  	second := SequenceNumber(result[2])
   103  
   104  	// Reduce *:* to *
   105  	if first.Last() && second.Last() {
   106  		return SequenceRange{Min: SequenceNumber("*"), Max: SequenceNumber("")}, nil
   107  	}
   108  
   109  	// Ensure "*" is always placed in 'Max'
   110  	if first.Last() && !second.Nil() {
   111  		return SequenceRange{Min: second, Max: first}, nil
   112  	}
   113  
   114  	// If both sequence numbers are integer values, we need to sort them
   115  	if first.IsValue() && second.IsValue() {
   116  		firstVal, _ := first.Value()
   117  		secondVal, _ := second.Value()
   118  		if firstVal > secondVal {
   119  			return SequenceRange{Min: second, Max: first}, nil
   120  		}
   121  	}
   122  
   123  	return SequenceRange{Min: first, Max: second}, nil
   124  }
   125  
   126  // InterpretSequenceSet creates a SequenceSet from the given string in the IMAP
   127  // format.
   128  func InterpretSequenceSet(imapSequenceSet string) (seqSet SequenceSet, err error) {
   129  	// Ensure the sequence set is valid
   130  	if !setRegexp.MatchString(imapSequenceSet) {
   131  		return nil, errInvalidSequenceSetString(imapSequenceSet)
   132  	}
   133  
   134  	ranges := strings.Split(imapSequenceSet, ",")
   135  
   136  	seqSet = make(SequenceSet, len(ranges))
   137  	for index, rng := range ranges {
   138  		seqSet[index], err = InterpretMessageRange(rng)
   139  		if err != nil {
   140  			return seqSet, err
   141  		}
   142  	}
   143  
   144  	return seqSet, nil
   145  }