-
Notifications
You must be signed in to change notification settings - Fork 111
/
SearchCondition.cs
438 lines (429 loc) · 19.7 KB
/
SearchCondition.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Linq;
namespace S22.Imap {
/// <summary>
/// Represents chainable search conditions that can be used with the Search method.
/// </summary>
public class SearchCondition {
/// <summary>
/// Finds all messages in the mailbox.
/// </summary>
/// <returns>A SearchCondition object representing the "all" search criterion.</returns>
public static SearchCondition All() {
return new SearchCondition { Field = Fields.All };
}
/// <summary>
/// Finds messages that contain the specified string in the header or body of the message.
/// </summary>
/// <param name="text">String to search messages for.</param>
/// <returns>A SearchCondition object representing the "text" search criterion.</returns>
/// <exception cref="ArgumentNullException">The text parameter is null.</exception>
public static SearchCondition Text(string text) {
text.ThrowIfNull("text");
return new SearchCondition { Field = Fields.Text, Value = text };
}
/// <summary>
/// Finds messages that contain the specified string in the envelope structure's BCC field.
/// </summary>
/// <param name="text">String to search the envelope structure's BCC field for.</param>
/// <returns>A SearchCondition object representing the "BCC" search criterion.</returns>
/// <exception cref="ArgumentNullException">The text parameter is null.</exception>
public static SearchCondition BCC(string text) {
text.ThrowIfNull("text");
return new SearchCondition { Field = Fields.BCC, Value = text };
}
/// <summary>
/// Finds messages whose internal date (disregarding time and timezone) is earlier than the
/// specified date.
/// </summary>
/// <param name="date">The date to compare the message's internal date with.</param>
/// <returns>A SearchCondition object representing the "Before" search criterion.</returns>
public static SearchCondition Before(DateTime date) {
return new SearchCondition { Field = Fields.Before, Value = date };
}
/// <summary>
/// Finds messages that contain the specified string in the body of the message.
/// </summary>
/// <param name="text">String to search the message body for.</param>
/// <returns>A SearchCondition object representing the "Body" search criterion.</returns>
/// <exception cref="ArgumentNullException">The text parameter is null.</exception>
public static SearchCondition Body(string text) {
text.ThrowIfNull("text");
return new SearchCondition { Field = Fields.Body, Value = text };
}
/// <summary>
/// Finds messages that contain the specified string in the envelope structure's CC field.
/// </summary>
/// <param name="text">String to search the envelope structure's CC field for.</param>
/// <returns>A SearchCondition object representing the "CC" search criterion.</returns>
/// <exception cref="ArgumentNullException">The text parameter is null.</exception>
public static SearchCondition Cc(string text) {
text.ThrowIfNull("text");
return new SearchCondition { Field = Fields.Cc, Value = text };
}
/// <summary>
/// Finds messages that contain the specified string in the envelope structure's FROM field.
/// </summary>
/// <param name="text">String to search the envelope structure's FROM field for.</param>
/// <returns>A SearchCondition object representing the "FROM" search criterion.</returns>
/// <exception cref="ArgumentNullException">The text parameter is null.</exception>
public static SearchCondition From(string text) {
text.ThrowIfNull("text");
return new SearchCondition { Field = Fields.From, Value = text };
}
/// <summary>
/// Finds messages that have a header with the specified field-name and that contains the
/// specified string in the text of the header.
/// </summary>
/// <param name="name">field-name of the header to search for.</param>
/// <param name="text">String to search for in the text of the header.</param>
/// <returns>A SearchCondition object representing the "HEADER" search criterion.</returns>
/// <remarks>
/// If the string to search is zero-length, this matches all messages that have a header line
/// with the specified field-name regardless of the contents.
/// </remarks>
/// <exception cref="ArgumentNullException">The name parameter or the text parameter is
/// null.</exception>
public static SearchCondition Header(string name, string text) {
name.ThrowIfNull("name");
text.ThrowIfNull("text");
return new SearchCondition { Field = Fields.Header, Quote = false,
Value = name + " " + text.QuoteString() };
}
/// <summary>
/// Finds messages with the specified keyword flag set.
/// </summary>
/// <param name="text">The keyword flag to search for.</param>
/// <returns>A SearchCondition object representing the "KEYWORD" search criterion.</returns>
/// <exception cref="ArgumentNullException">The text parameter is null.</exception>
public static SearchCondition Keyword(string text) {
text.ThrowIfNull("text");
return new SearchCondition { Field = Fields.Keyword, Value = text };
}
/// <summary>
/// Finds messages with a size larger than the specified number of bytes.
/// </summary>
/// <param name="size">Minimum size, in bytes a message must have to be included in the search
/// result.</param>
/// <returns>A SearchCondition object representing the "LARGER" search criterion.</returns>
public static SearchCondition Larger(long size) {
return new SearchCondition { Field = Fields.Larger, Value = size };
}
/// <summary>
/// Finds messages with a size smaller than the specified number of bytes.
/// </summary>
/// <param name="size">Maximum size, in bytes a message must have to be included in the search
/// result.</param>
/// <returns>A SearchCondition object representing the "SMALLER" search criterion.</returns>
public static SearchCondition Smaller(long size) {
return new SearchCondition { Field = Fields.Smaller, Value = size };
}
/// <summary>
/// Finds messages whose Date: header (disregarding time and timezone) is earlier than the
/// specified date.
/// </summary>
/// <param name="date">The date to compare the Date: header field with.</param>
/// <returns>A SearchCondition object representing the "SENTBEFORE" search criterion.</returns>
public static SearchCondition SentBefore(DateTime date) {
return new SearchCondition { Field = Fields.SentBefore, Value = date };
}
/// <summary>
/// Finds messages whose Date: header (disregarding time and timezone) is within the specified
/// date.
/// </summary>
/// <param name="date">The date to compare the Date: header field with.</param>
/// <returns>A SearchCondition object representing the "SENTON" search criterion.</returns>
public static SearchCondition SentOn(DateTime date) {
return new SearchCondition { Field = Fields.SentOn, Value = date };
}
/// <summary>
/// Finds messages whose Date: header (disregarding time and timezone) is within or later than
/// the specified date.
/// </summary>
/// <param name="date">The date to compare the Date: header field with.</param>
/// <returns>A SearchCondition object representing the "SENTSINCE" search criterion.</returns>
public static SearchCondition SentSince(DateTime date) {
return new SearchCondition { Field = Fields.SentSince, Value = date };
}
/// <summary>
/// Finds messages that contain the specified string in the envelope structure's SUBJECT field.
/// </summary>
/// <param name="text">String to search the envelope structure's SUBJECT field for.</param>
/// <returns>A SearchCondition object representing the "SUBJECT" search criterion.</returns>
/// <exception cref="ArgumentNullException">The text parameter is null.</exception>
public static SearchCondition Subject(string text) {
text.ThrowIfNull("text");
return new SearchCondition { Field = Fields.Subject, Value = text };
}
/// <summary>
/// Finds messages that contain the specified string in the envelope structure's TO field.
/// </summary>
/// <param name="text">String to search the envelope structure's TO field for.</param>
/// <returns>A SearchCondition object representing the "TO" search criterion.</returns>
/// <exception cref="ArgumentNullException">The text parameter is null.</exception>
public static SearchCondition To(string text) {
text.ThrowIfNull("text");
return new SearchCondition { Field = Fields.To, Value = text };
}
/// <summary>
/// Finds messages with unique identifiers corresponding to the specified unique identifier set.
/// </summary>
/// <param name="uids">One or several unique identifiers (UID).</param>
/// <returns>A SearchCondition object representing the "UID" search criterion.</returns>
public static SearchCondition UID(params uint[] uids) {
return new SearchCondition { Field = Fields.UID,
Value = uids };
}
/// <summary>
/// Finds messages with a unique identifier greater than the specified unique identifier.
/// </summary>
/// <param name="uid">A unique identifier (UID).</param>
/// <returns>A SearchCondition object representing the "UID" search criterion.</returns>
/// <remarks>
/// Because of the nature of the IMAP search mechanism, the result set will always contain the
/// UID of the last message in the mailbox, even if said UID is smaller than the UID specified.
/// </remarks>
public static SearchCondition GreaterThan(uint uid) {
return new SearchCondition { Field = Fields.UID,
Value = (uid + 1).ToString() + ":*", Quote = false };
}
/// <summary>
/// Finds messages with a unique identifier less than the specified unique identifier.
/// </summary>
/// <param name="uid">A unique identifier (UID).</param>
/// <returns>A SearchCondition object representing the "UID" search criterion.</returns>
public static SearchCondition LessThan(uint uid) {
return new SearchCondition { Field = Fields.UID,
Value = "1:" + (uid - 1).ToString(), Quote = false };
}
/// <summary>
/// Finds messages that do not have the specified keyword flag set.
/// </summary>
/// <param name="text">The IMAP keyword flag to search for.</param>
/// <returns>A SearchCondition object representing the "UNKEYWORD" search criterion.</returns>
/// <exception cref="ArgumentNullException">The text parameter is null.</exception>
public static SearchCondition Unkeyword(string text) {
text.ThrowIfNull("text");
return new SearchCondition { Field = Fields.Unkeyword, Value = text };
}
/// <summary>
/// Finds messages that have the \Answered flag set.
/// </summary>
/// <returns>A SearchCondition object representing the "ANSWERED" search criterion.</returns>
public static SearchCondition Answered() {
return new SearchCondition { Field = Fields.Answered };
}
/// <summary>
/// Finds messages that have the \Deleted flag set.
/// </summary>
/// <returns>A SearchCondition object representing the "DELETED" search criterion.</returns>
public static SearchCondition Deleted() {
return new SearchCondition { Field = Fields.Deleted };
}
/// <summary>
/// Finds messages that have the \Draft flag set.
/// </summary>
/// <returns>A SearchCondition object representing the "DRAFT" search criterion.</returns>
public static SearchCondition Draft() {
return new SearchCondition { Field = Fields.Draft };
}
/// <summary>
/// Finds messages that have the \Flagged flag set.
/// </summary>
/// <returns>A SearchCondition object representing the "FLAGGED" search criterion.</returns>
public static SearchCondition Flagged() {
return new SearchCondition { Field = Fields.Flagged };
}
/// <summary>
/// Finds messages that have the \Recent flag set but not the \Seen flag.
/// </summary>
/// <returns>A SearchCondition object representing the "NEW" search criterion.</returns>
public static SearchCondition New() {
return new SearchCondition { Field = Fields.New };
}
/// <summary>
/// Finds messages that do not have the \Recent flag set.
/// </summary>
/// <returns>A SearchCondition object representing the "OLD" search criterion.</returns>
public static SearchCondition Old() {
return new SearchCondition { Field = Fields.Old };
}
/// <summary>
/// Finds messages that have the \Recent flag set.
/// </summary>
/// <returns>A SearchCondition object representing the "RECENT" search criterion.</returns>
public static SearchCondition Recent() {
return new SearchCondition { Field = Fields.Recent };
}
/// <summary>
/// Finds messages that have the \Seen flag set.
/// </summary>
/// <returns>A SearchCondition object representing the "SEEN" search criterion.</returns>
public static SearchCondition Seen() {
return new SearchCondition { Field = Fields.Seen };
}
/// <summary>
/// Finds messages that do not have the \Answered flag set.
/// </summary>
/// <returns>A SearchCondition object representing the "UNANSWERED" search criterion.</returns>
public static SearchCondition Unanswered() {
return new SearchCondition { Field = Fields.Unanswered };
}
/// <summary>
/// Finds messages that do not have the \Deleted flag set.
/// </summary>
/// <returns>A SearchCondition object representing the "UNDELETED" search criterion.</returns>
public static SearchCondition Undeleted() {
return new SearchCondition { Field = Fields.Undeleted };
}
/// <summary>
/// Finds messages that do not have the \Draft flag set.
/// </summary>
/// <returns>A SearchCondition object representing the "UNDRAFT" search criterion.</returns>
public static SearchCondition Undraft() {
return new SearchCondition { Field = Fields.Undraft };
}
/// <summary>
/// Finds messages that do not have the \Flagged flag set.
/// </summary>
/// <returns>A SearchCondition object representing the "UNFLAGGED" search criterion.</returns>
public static SearchCondition Unflagged() {
return new SearchCondition { Field = Fields.Unflagged };
}
/// <summary>
/// Finds messages that do not have the \Seen flag set.
/// </summary>
/// <returns>A SearchCondition object representing the "UNSEEN" search criterion.</returns>
public static SearchCondition Unseen() {
return new SearchCondition { Field = Fields.Unseen };
}
/// <summary>
/// Logically ANDs multiple search conditions, meaning a message will only be included in the
/// search result if both of the ANDed conditions are met.
/// </summary>
/// <param name="other">A search condition to logically AND this SearchCondition instance
/// with.</param>
/// <returns>A new SearchCondition instance which can be further chained with other search
/// conditions.</returns>
/// <exception cref="ArgumentNullException">The other parameter is null.</exception>
public SearchCondition And(SearchCondition other) {
other.ThrowIfNull("other");
return Join(string.Empty, this, other);
}
/// <summary>
/// Logically negates search conditions, meaning a message will only be included in the search
/// result if the specified conditions are not met.
/// </summary>
/// <param name="other">A search condition that must not be met by a message for it to be
/// included in the search result set.</param>
/// <returns>A new SearchCondition instance which can be further chained with other search
/// conditions.</returns>
/// <exception cref="ArgumentNullException">The other parameter is null.</exception>
public SearchCondition Not(SearchCondition other) {
other.ThrowIfNull("other");
return Join("NOT", this, other);
}
/// <summary>
/// Logically ORs multiple search conditions, meaning a message will be included in the search
/// result if it meets at least either of the conditions.
/// </summary>
/// <param name="other">A search condition to logically OR this SearchCondition instance
/// with.</param>
/// <returns>A new SearchCondition instance which can be further chained with other search
/// conditions.</returns>
/// <exception cref="ArgumentNullException">The other parameter is null.</exception>
public SearchCondition Or(SearchCondition other) {
other.ThrowIfNull("other");
return Join("OR", this, other);
}
/// <summary>
/// The search keys which can be used with the IMAP SEARCH command, as are defined in section
/// 6.4.4 of RFC 3501.
/// </summary>
enum Fields {
BCC, Before, Body, Cc, From, Header, Keyword,
Larger, On, SentBefore, SentOn, SentSince, Since, Smaller, Subject,
Text, To, UID, Unkeyword, All, Answered, Deleted, Draft, Flagged,
New, Old, Recent, Seen, Unanswered, Undeleted, Undraft, Unflagged, Unseen
}
object Value { get; set; }
Fields? Field { get; set; }
List<SearchCondition> Conditions { get; set; }
string Operator { get; set; }
bool Quote = true;
/// <summary>
/// Joins two SearchCondition objects into a new one using the specified logical operator.
/// </summary>
/// <param name="condition">The logical operator to use for joining the search conditions.
/// Possible values are "OR", "NOT" and the empty string "" which denotes a logical AND.</param>
/// <param name="left">The first SearchCondition object</param>
/// <param name="right">The second SearchCondition object</param>
/// <returns>A new SearchCondition object representing the two search conditions joined by the
/// specified logical operator.</returns>
static SearchCondition Join(string condition, SearchCondition left,
SearchCondition right) {
return new SearchCondition {
Operator = condition.ToUpper(),
Conditions = new List<SearchCondition> { left, right }
};
}
/// <summary>
/// Concatenates the members of a collection, using the specified separator between each
/// member.
/// </summary>
/// <typeparam name="T">The type of the members of values.</typeparam>
/// <param name="seperator">The string to use as a separator.</param>
/// <param name="values">A collection that contains the objects to concatenate.</param>
/// <returns>A string that consists of the members of values delimited by the separator
/// string. If values has no members, the method returns System.String.Empty.</returns>
/// <exception cref="ArgumentNullException">The values parameter is null.</exception>
/// <remarks>This is already part of the String class in .NET 4.0 and newer but is needed
/// for backwards compatibility with .NET 3.5.</remarks>
static string Join<T>(string seperator, IEnumerable<T> values) {
values.ThrowIfNull("values");
IList<string> list = new List<string>();
foreach (T v in values)
list.Add(v.ToString());
return string.Join(seperator, list.ToArray());
}
/// <summary>
/// Constructs a string from the SearchCondition object using the proper syntax as is required
/// for the IMAP SEARCH command.
/// </summary>
/// <returns>A string representing this SearchCondition instance that can be used with the IMAP
/// SEARCH command.</returns>
public override string ToString() {
if (Conditions != null && Conditions.Count > 0 && Operator != null)
return (Operator.ToUpper() + " (" + Join(") (", Conditions) + ")").Trim();
StringBuilder builder = new StringBuilder();
if (Field != null)
builder.Append(Field.ToString().ToUpper());
object Val = Value;
if (Val != null) {
if (Field != null)
builder.Append(" ");
if (Val is string) {
string s = (string)Val;
// If the string contains non-ASCII characters we must use the somewhat cumbersome literal
// form as is outlined in RFC 3501 Section 4.3.
if (!s.IsASCII()) {
builder.AppendLine("{" + Encoding.UTF8.GetBytes(s).Length + "}");
} else {
if (Quote)
Val = ((string)Val).QuoteString();
}
} else if (Val is DateTime) {
Val = ((DateTime)Val).ToString("dd-MMM-yyyy",
CultureInfo.InvariantCulture).QuoteString();
} else if (Val is uint[]) {
Val = Join<uint>(",", (uint[])Val);
}
builder.Append(Val);
}
return builder.ToString();
}
}
}