-
Notifications
You must be signed in to change notification settings - Fork 124
/
Copy pathcommand-builder.js
234 lines (210 loc) · 6.14 KB
/
command-builder.js
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
import { parser } from 'emailjs-imap-handler'
import { encode } from 'emailjs-mime-codec'
import { encode as encodeBase64 } from 'emailjs-base64'
import {
fromTypedArray,
toTypedArray
} from './common'
/**
* Builds a FETCH command
*
* @param {String} sequence Message range selector
* @param {Array} items List of elements to fetch (eg. `['uid', 'envelope']`).
* @param {Object} [options] Optional options object. Use `{byUid:true}` for `UID FETCH`
* @returns {Object} Structured IMAP command
*/
export function buildFETCHCommand (sequence, items, options) {
const command = {
command: options.byUid ? 'UID FETCH' : 'FETCH',
attributes: [{
type: 'SEQUENCE',
value: sequence
}]
}
if (options.valueAsString !== undefined) {
command.valueAsString = options.valueAsString
}
let query = []
items.forEach((item) => {
item = item.toUpperCase().trim()
if (/^\w+$/.test(item)) {
// alphanum strings can be used directly
query.push({
type: 'ATOM',
value: item
})
} else if (item) {
try {
// parse the value as a fake command, use only the attributes block
const cmd = parser(toTypedArray('* Z ' + item))
query = query.concat(cmd.attributes || [])
} catch (e) {
// if parse failed, use the original string as one entity
query.push({
type: 'ATOM',
value: item
})
}
}
})
if (query.length === 1) {
query = query.pop()
}
command.attributes.push(query)
if (options.changedSince) {
command.attributes.push([{
type: 'ATOM',
value: 'CHANGEDSINCE'
}, {
type: 'ATOM',
value: options.changedSince
}])
}
return command
}
/**
* Builds a login token for XOAUTH2 authentication command
*
* @param {String} user E-mail address of the user
* @param {String} token Valid access token for the user
* @return {String} Base64 formatted login token
*/
export function buildXOAuth2Token (user = '', token) {
const authData = [
`user=${user}`,
`auth=Bearer ${token}`,
'',
''
]
return encodeBase64(authData.join('\x01'))
}
/**
* Compiles a search query into an IMAP command. Queries are composed as objects
* where keys are search terms and values are term arguments. Only strings,
* numbers and Dates are used. If the value is an array, the members of it
* are processed separately (use this for terms that require multiple params).
* If the value is a Date, it is converted to the form of "01-Jan-1970".
* Subqueries (OR, NOT) are made up of objects
*
* {unseen: true, header: ["subject", "hello world"]};
* SEARCH UNSEEN HEADER "subject" "hello world"
*
* @param {Object} query Search query
* @param {Object} [options] Option object
* @param {Boolean} [options.byUid] If ture, use UID SEARCH instead of SEARCH
* @return {Object} IMAP command object
*/
export function buildSEARCHCommand (query = {}, options = {}) {
const command = {
command: options.byUid ? 'UID SEARCH' : 'SEARCH'
}
let isAscii = true
const buildTerm = (query) => {
let list = []
Object.keys(query).forEach((key) => {
let params = []
const formatDate = (date) => date.toUTCString().replace(/^\w+, 0?(\d+) (\w+) (\d+).*/, '$1-$2-$3')
const escapeParam = (param) => {
if (typeof param === 'number') {
return {
type: 'number',
value: param
}
} else if (typeof param === 'string') {
if (/[\u0080-\uFFFF]/.test(param)) {
isAscii = false
return {
type: 'literal',
value: fromTypedArray(encode(param)) // cast unicode string to pseudo-binary as imap-handler compiles strings as octets
}
}
return {
type: 'string',
value: param
}
} else if (Object.prototype.toString.call(param) === '[object Date]') {
// RFC 3501 allows for dates to be placed in
// double-quotes or left without quotes. Some
// servers (Yandex), do not like the double quotes,
// so we treat the date as an atom.
return {
type: 'atom',
value: formatDate(param)
}
} else if (Array.isArray(param)) {
return param.map(escapeParam)
} else if (typeof param === 'object') {
return buildTerm(param)
}
}
params.push({
type: 'atom',
value: key.toUpperCase()
});
[].concat(query[key] || []).forEach((param) => {
switch (key.toLowerCase()) {
case 'uid':
param = {
type: 'sequence',
value: param
}
break
// The Gmail extension values of X-GM-THRID and
// X-GM-MSGID are defined to be unsigned 64-bit integers
// and they must not be quoted strings or the server
// will report a parse error.
case 'x-gm-thrid':
case 'x-gm-msgid':
param = {
type: 'number',
value: param
}
break
default:
param = escapeParam(param)
}
if (param) {
params = params.concat(param || [])
}
})
list = list.concat(params || [])
})
return list
}
command.attributes = buildTerm(query)
// If any string input is using 8bit bytes, prepend the optional CHARSET argument
if (!isAscii) {
command.attributes.unshift({
type: 'atom',
value: 'UTF-8'
})
command.attributes.unshift({
type: 'atom',
value: 'CHARSET'
})
}
return command
}
/**
* Creates an IMAP STORE command from the selected arguments
*/
export function buildSTORECommand (sequence, action = '', flags = [], options = {}) {
const command = {
command: options.byUid ? 'UID STORE' : 'STORE',
attributes: [{
type: 'sequence',
value: sequence
}]
}
command.attributes.push({
type: 'atom',
value: action.toUpperCase() + (options.silent ? '.SILENT' : '')
})
command.attributes.push(flags.map((flag) => {
return {
type: 'atom',
value: flag
}
}))
return command
}