@email-utils/validator-syntax 0.0.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,336 @@
1
+ // VALID EMAIL
2
+ // Attempting to follow rules from https://en.wikipedia.org/wiki/Email_address.
3
+ // Checks if the email is valid.
4
+ import * as tlds from 'tlds/index.json'
5
+ class EmailSyntaxValidator {
6
+ defaultConfig: EmailValidatorConfig = {
7
+ local: {
8
+ alphaUpper: true,
9
+ alphaLower: true,
10
+ numeric: true,
11
+ period: true,
12
+ printable: true,
13
+ quote: true,
14
+ hyphen: true,
15
+ spaces: true,
16
+ },
17
+ domain: {
18
+ alphaUpper: true,
19
+ alphaLower: true,
20
+ numeric: true,
21
+ period: true,
22
+ hyphen: true,
23
+ tld: true,
24
+ localhost: false,
25
+ charsBeforeDot: 1, // -1 means don't check.
26
+ charsAfterDot: 2, // -1 means don't check.
27
+ },
28
+ }
29
+ config: EmailValidatorConfig = this.defaultConfig
30
+ chars = {
31
+ alphaLower: 'abcdefghijklmnopqrstuvwxyz',
32
+ alphaUpper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
33
+ numeric: '0123456789',
34
+ dot: ',',
35
+ printable: "!#$%&'*+/=?^_`{|}~",
36
+ space: ' ',
37
+ quote: '"',
38
+ hyphen: '-',
39
+ period: '.',
40
+ }
41
+ email = ''
42
+ originalEmail = ''
43
+
44
+ constructor(configParam: EmailValidatorParam = {} as EmailValidatorParam) {
45
+ let key: keyof EmailValidatorConfig
46
+
47
+ console.log('validate config')
48
+
49
+ for (key in configParam) {
50
+ const configGroup = configParam[key]
51
+ for (const subKey in configGroup) {
52
+ const subKeyType = subKey as keyof typeof configGroup
53
+ this.config[key][subKeyType] = configGroup[subKeyType]
54
+ }
55
+ }
56
+ }
57
+ private gatherChars(
58
+ charConfig: LocalValidateConfig | DomainValidateConfig
59
+ ): string[] {
60
+ let characters = ''
61
+
62
+ if (charConfig.alphaUpper === true) {
63
+ characters += this.chars.alphaUpper
64
+ }
65
+ if (charConfig.alphaLower === true) {
66
+ characters += this.chars.alphaLower
67
+ }
68
+ if (charConfig.numeric === true) {
69
+ characters += this.chars.numeric
70
+ }
71
+
72
+ if (charConfig.hyphen === true) {
73
+ characters += this.chars.hyphen
74
+ }
75
+ if ('printable' in charConfig && charConfig.printable === true) {
76
+ characters += this.chars.printable
77
+ }
78
+
79
+ if ('spaces' in charConfig && charConfig.spaces === true) {
80
+ characters += this.chars.space
81
+ }
82
+
83
+ if ('quote' in charConfig && charConfig.quote === true) {
84
+ characters += this.chars.quote
85
+ }
86
+
87
+ if (charConfig.period === true) {
88
+ characters += this.chars.period
89
+ }
90
+
91
+ return characters.split('')
92
+ }
93
+ private validateGeneral(): boolean {
94
+ // Confirm that there is an at symbol and it isn't the first character.
95
+ const atSymbolIndex = this.email.indexOf('@')
96
+ if (atSymbolIndex < 1) {
97
+ return false
98
+ }
99
+
100
+ return true
101
+ }
102
+ private validateChars(chars: string[], charsToValidate: string[]): boolean {
103
+ let quoteOpen = false
104
+ for (let l = 0; l < charsToValidate.length; l++) {
105
+ const letter = charsToValidate[l]
106
+ if (this.config.local.quote) {
107
+ if (letter === '"') {
108
+ quoteOpen = !quoteOpen
109
+
110
+ if (l !== 0 && l !== charsToValidate.length - 1) {
111
+ if (quoteOpen && !(charsToValidate[l - 1] === '.')) {
112
+ return false
113
+ } else if (!quoteOpen && !(charsToValidate[l + 1] === '.')) {
114
+ return false
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ const letterInAllowed = chars.indexOf(letter) !== -1
121
+
122
+ if (!letterInAllowed) {
123
+ return false
124
+ }
125
+ }
126
+
127
+ return true
128
+ }
129
+ private validateLocal(): boolean {
130
+ const validChars = this.gatherChars(this.config.local)
131
+ let localLetters = this.email.slice(0, this.email.indexOf('@')).split('')
132
+
133
+ if (this.config.local.printable) {
134
+ const percentInLocalIndex = localLetters.indexOf('%')
135
+
136
+ if (percentInLocalIndex !== -1) {
137
+ localLetters = localLetters.slice(0, percentInLocalIndex)
138
+ }
139
+
140
+ const bangInLocalIndex = localLetters.indexOf('!')
141
+
142
+ if (bangInLocalIndex !== -1) {
143
+ localLetters = localLetters.slice(bangInLocalIndex)
144
+ }
145
+ }
146
+
147
+ // Validate local length
148
+ if (localLetters.length > 64) {
149
+ return false
150
+ }
151
+
152
+ const charsValid = this.validateChars(validChars, localLetters)
153
+
154
+ if (!charsValid) {
155
+ return false
156
+ }
157
+
158
+ // Make sure there are not two periods together.
159
+ const indexOfDoublePeriods = this.email.indexOf('..')
160
+
161
+ if (indexOfDoublePeriods !== -1) {
162
+ // If double periods before at symbol it's invalid.
163
+ const atSymbolIndex = this.email.indexOf('@')
164
+ if (indexOfDoublePeriods > atSymbolIndex) {
165
+ return false
166
+ }
167
+
168
+ // If double period is not within quotes.
169
+ const indexOfFirstQuoteInLocal = localLetters.indexOf('"')
170
+ const indexOfLastQuoteInLocal =
171
+ localLetters.length -
172
+ `${localLetters}`.split('').reverse().join('').indexOf('"') -
173
+ 1
174
+
175
+ if (indexOfFirstQuoteInLocal < 0 || indexOfLastQuoteInLocal < 0) {
176
+ return false
177
+ }
178
+
179
+ if (
180
+ indexOfFirstQuoteInLocal >= indexOfDoublePeriods ||
181
+ indexOfDoublePeriods >= indexOfLastQuoteInLocal
182
+ ) {
183
+ return false
184
+ }
185
+ }
186
+
187
+ // Confirm that there is no spaces that are not surrounded by quotes.
188
+ const indexOfSpace = localLetters.indexOf(' ')
189
+
190
+ if (
191
+ indexOfSpace !== -1 &&
192
+ (localLetters[indexOfSpace + 1] !== '"' ||
193
+ localLetters[indexOfSpace - 1] !== '"')
194
+ ) {
195
+ return false
196
+ }
197
+
198
+ return true
199
+ }
200
+ private validateDomainParts(domain: string): boolean {
201
+ const dotIndex = domain.lastIndexOf('.')
202
+
203
+ if (dotIndex === -1 && this.config.domain.localhost === false) {
204
+ return false
205
+ }
206
+
207
+ if (dotIndex !== -1) {
208
+ if (this.config.domain.charsBeforeDot !== -1) {
209
+ if (dotIndex <= this.config.domain.charsBeforeDot) {
210
+ return false
211
+ }
212
+ }
213
+
214
+ if (this.config.domain.charsAfterDot !== -1) {
215
+ if (dotIndex === domain.length - this.config.domain.charsAfterDot) {
216
+ return false
217
+ }
218
+ }
219
+
220
+ if (this.config.domain.tld) {
221
+ const tld = domain.slice(dotIndex + 1)
222
+
223
+ let tldExists = false
224
+
225
+ for (let t = 0; t < tlds.length; t++) {
226
+ const currentTld = tlds[t]
227
+
228
+ if (tld.toLowerCase() === currentTld) {
229
+ tldExists = true
230
+ }
231
+
232
+ if (tldExists) {
233
+ break
234
+ }
235
+ }
236
+
237
+ if (!tldExists) {
238
+ return false
239
+ }
240
+ }
241
+ }
242
+
243
+ return true
244
+ }
245
+ private validateDomain(): boolean {
246
+ // Confirm that there is a dot at least 1 character after the at and 2 characters before the end.
247
+ const domain = this.email.slice(this.email.indexOf('@') + 1)
248
+
249
+ const domainPartsValid = this.validateDomainParts(domain)
250
+
251
+ if (!domainPartsValid) {
252
+ return false
253
+ }
254
+
255
+ const validChars = this.gatherChars(this.config.domain)
256
+ const domainLetters = domain.split('')
257
+
258
+ if (this.config.local.printable) {
259
+ const localLetters = this.email
260
+ .slice(0, this.email.indexOf('@'))
261
+ .split('')
262
+
263
+ const percentInLocalIndex = localLetters.indexOf('%')
264
+
265
+ if (percentInLocalIndex !== -1) {
266
+ const relayDomain = localLetters.slice(percentInLocalIndex + 1)
267
+
268
+ const relayDomainPartsValid = this.validateDomainParts(
269
+ relayDomain.join('')
270
+ )
271
+
272
+ if (!relayDomainPartsValid) {
273
+ return false
274
+ }
275
+
276
+ const charsValid = this.validateChars(validChars, relayDomain)
277
+
278
+ if (!charsValid) {
279
+ return false
280
+ }
281
+ }
282
+
283
+ const bangInLocalIndex = localLetters.indexOf('!')
284
+
285
+ if (bangInLocalIndex !== -1) {
286
+ const relayDomain = localLetters.slice(0, bangInLocalIndex)
287
+
288
+ const relayDomainPartsValid = this.validateDomainParts(
289
+ relayDomain.join('')
290
+ )
291
+
292
+ if (!relayDomainPartsValid) {
293
+ return false
294
+ }
295
+
296
+ const charsValid = this.validateChars(validChars, relayDomain)
297
+
298
+ if (!charsValid) {
299
+ return false
300
+ }
301
+ }
302
+ }
303
+
304
+ const charsValid = this.validateChars(validChars, domainLetters)
305
+
306
+ if (!charsValid) {
307
+ return false
308
+ }
309
+
310
+ return true
311
+ }
312
+
313
+ public async validate(email: string): Promise<boolean> {
314
+ this.email = email
315
+ // Need to add support for special local chars that are properly quoted.
316
+ // Need to add support for quotes in ().
317
+ const generalValid = this.validateGeneral()
318
+ if (!generalValid) {
319
+ return false
320
+ }
321
+
322
+ const localValid = this.validateLocal()
323
+ if (!localValid) {
324
+ return false
325
+ }
326
+
327
+ const domainValid = this.validateDomain()
328
+ if (!domainValid) {
329
+ return false
330
+ }
331
+
332
+ return true
333
+ }
334
+ }
335
+
336
+ export default EmailSyntaxValidator