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 }