-
Notifications
You must be signed in to change notification settings - Fork 100
/
InternetAddress.java
1527 lines (1414 loc) · 46.7 KB
/
InternetAddress.java
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
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.internet;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.Locale;
import java.nio.charset.StandardCharsets;
import jakarta.mail.*;
/**
* This class represents an Internet email address using the syntax
* of <a href="http://www.ietf.org/rfc/rfc822.txt" target="_top">RFC822</a>.
* Typical address syntax is of the form "user@host.domain" or
* "Personal Name <user@host.domain>".
*
* @author Bill Shannon
* @author John Mani
*/
public class InternetAddress extends Address implements Cloneable {
protected String address; // email address
/**
* The personal name.
*/
protected String personal;
/**
* The RFC 2047 encoded version of the personal name. <p>
*
* This field and the <code>personal</code> field track each
* other, so if a subclass sets one of these fields directly, it
* should set the other to <code>null</code>, so that it is
* suitably recomputed.
*/
protected String encodedPersonal;
private static final long serialVersionUID = -7507595530758302903L;
private static final boolean ignoreBogusGroupName =
MimeUtility.getBooleanSystemProperty(
"mail.mime.address.ignorebogusgroupname", true);
private static final boolean useCanonicalHostName =
MimeUtility.getBooleanSystemProperty(
"mail.mime.address.usecanonicalhostname", true);
private static final boolean allowUtf8 =
MimeUtility.getBooleanSystemProperty("mail.mime.allowutf8", false);
/**
* Default constructor.
*/
public InternetAddress() { }
/**
* Constructor. <p>
*
* Parse the given string and create an InternetAddress.
* See the <code>parse</code> method for details of the parsing.
* The address is parsed using "strict" parsing.
* This constructor does <b>not</b> perform the additional
* syntax checks that the
* <code>InternetAddress(String address, boolean strict)</code>
* constructor does when <code>strict</code> is <code>true</code>.
* This constructor is equivalent to
* <code>InternetAddress(address, false)</code>.
*
* @param address the address in RFC822 format
* @exception AddressException if the parse failed
*/
public InternetAddress(String address) throws AddressException {
// use our address parsing utility routine to parse the string
InternetAddress a[] = parse(address, true);
// if we got back anything other than a single address, it's an error
if (a.length != 1)
throw new AddressException("Illegal address", address);
/*
* Now copy the contents of the single address we parsed
* into the current object, which will be returned from the
* constructor.
* XXX - this sure is a round-about way of getting this done.
*/
this.address = a[0].address;
this.personal = a[0].personal;
this.encodedPersonal = a[0].encodedPersonal;
}
/**
* Parse the given string and create an InternetAddress.
* If <code>strict</code> is false, the detailed syntax of the
* address isn't checked.
*
* @param address the address in RFC822 format
* @param strict enforce RFC822 syntax
* @exception AddressException if the parse failed
* @since JavaMail 1.3
*/
public InternetAddress(String address, boolean strict)
throws AddressException {
this(address);
if (strict) {
if (isGroup())
getGroup(true); // throw away the result
else
checkAddress(this.address, true, true);
}
}
/**
* Construct an InternetAddress given the address and personal name.
* The address is assumed to be a syntactically valid RFC822 address.
*
* @param address the address in RFC822 format
* @param personal the personal name
* @exception UnsupportedEncodingException if the personal name
* can't be encoded in the given charset
*/
public InternetAddress(String address, String personal)
throws UnsupportedEncodingException {
this(address, personal, null);
}
/**
* Construct an InternetAddress given the address and personal name.
* The address is assumed to be a syntactically valid RFC822 address.
*
* @param address the address in RFC822 format
* @param personal the personal name
* @param charset the MIME charset for the name
* @exception UnsupportedEncodingException if the personal name
* can't be encoded in the given charset
*/
public InternetAddress(String address, String personal, String charset)
throws UnsupportedEncodingException {
this.address = address;
setPersonal(personal, charset);
}
/**
* Return a copy of this InternetAddress object.
* @since JavaMail 1.2
*/
@Override
public Object clone() {
InternetAddress a = null;
try {
a = (InternetAddress)super.clone();
} catch (CloneNotSupportedException e) {} // Won't happen
return a;
}
/**
* Return the type of this address. The type of an InternetAddress
* is "rfc822".
*/
@Override
public String getType() {
return "rfc822";
}
/**
* Set the email address.
*
* @param address email address
*/
public void setAddress(String address) {
this.address = address;
}
/**
* Set the personal name. If the name contains non US-ASCII
* characters, then the name will be encoded using the specified
* charset as per RFC 2047. If the name contains only US-ASCII
* characters, no encoding is done and the name is used as is.
*
* @param name personal name
* @param charset MIME charset to be used to encode the name as
* per RFC 2047
* @see #setPersonal(String)
* @exception UnsupportedEncodingException if the charset encoding
* fails.
*/
public void setPersonal(String name, String charset)
throws UnsupportedEncodingException {
personal = name;
if (name != null)
encodedPersonal = MimeUtility.encodeWord(name, charset, null);
else
encodedPersonal = null;
}
/**
* Set the personal name. If the name contains non US-ASCII
* characters, then the name will be encoded using the platform's
* default charset. If the name contains only US-ASCII characters,
* no encoding is done and the name is used as is.
*
* @param name personal name
* @see #setPersonal(String name, String charset)
* @exception UnsupportedEncodingException if the charset encoding
* fails.
*/
public void setPersonal(String name)
throws UnsupportedEncodingException {
personal = name;
if (name != null)
encodedPersonal = MimeUtility.encodeWord(name);
else
encodedPersonal = null;
}
/**
* Get the email address.
* @return email address
*/
public String getAddress() {
return address;
}
/**
* Get the personal name. If the name is encoded as per RFC 2047,
* it is decoded and converted into Unicode. If the decoding or
* conversion fails, the raw data is returned as is.
*
* @return personal name
*/
public String getPersonal() {
if (personal != null)
return personal;
if (encodedPersonal != null) {
try {
personal = MimeUtility.decodeText(encodedPersonal);
return personal;
} catch (Exception ex) {
// 1. ParseException: either its an unencoded string or
// it can't be parsed
// 2. UnsupportedEncodingException: can't decode it.
return encodedPersonal;
}
}
// No personal or encodedPersonal, return null
return null;
}
/**
* Convert this address into a RFC 822 / RFC 2047 encoded address.
* The resulting string contains only US-ASCII characters, and
* hence is mail-safe.
*
* @return possibly encoded address string
*/
@Override
public String toString() {
String a = address == null ? "" : address;
if (encodedPersonal == null && personal != null)
try {
encodedPersonal = MimeUtility.encodeWord(personal);
} catch (UnsupportedEncodingException ex) { }
if (encodedPersonal != null)
return quotePhrase(encodedPersonal) + " <" + a + ">";
else if (isGroup() || isSimple())
return a;
else
return "<" + a + ">";
}
/**
* Returns a properly formatted address (RFC 822 syntax) of
* Unicode characters.
*
* @return Unicode address string
* @since JavaMail 1.2
*/
public String toUnicodeString() {
String p = getPersonal();
if (p != null)
return quotePhrase(p) + " <" + address + ">";
else if (isGroup() || isSimple())
return address;
else
return "<" + address + ">";
}
/*
* quotePhrase() quotes the words within a RFC822 phrase.
*
* This is tricky, since a phrase is defined as 1 or more
* RFC822 words, separated by LWSP. Now, a word that contains
* LWSP is supposed to be quoted, and this is exactly what the
* MimeUtility.quote() method does. However, when dealing with
* a phrase, any LWSP encountered can be construed to be the
* separator between words, and not part of the words themselves.
* To deal with this funkiness, we have the below variant of
* MimeUtility.quote(), which essentially ignores LWSP when
* deciding whether to quote a word.
*
* It aint pretty, but it gets the job done :)
*/
private static final String rfc822phrase =
HeaderTokenizer.RFC822.replace(' ', '\0').replace('\t', '\0');
private static String quotePhrase(String phrase) {
int len = phrase.length();
boolean needQuoting = false;
for (int i = 0; i < len; i++) {
char c = phrase.charAt(i);
if (c == '"' || c == '\\') {
// need to escape them and then quote the whole string
StringBuilder sb = new StringBuilder(len + 3);
sb.append('"');
for (int j = 0; j < len; j++) {
char cc = phrase.charAt(j);
if (cc == '"' || cc == '\\')
// Escape the character
sb.append('\\');
sb.append(cc);
}
sb.append('"');
return sb.toString();
} else if ((c < 040 && c != '\r' && c != '\n' && c != '\t') ||
(c >= 0177 && !allowUtf8) || rfc822phrase.indexOf(c) >= 0)
// These characters cause the string to be quoted
needQuoting = true;
}
if (needQuoting) {
StringBuilder sb = new StringBuilder(len + 2);
sb.append('"').append(phrase).append('"');
return sb.toString();
} else
return phrase;
}
private static String unquote(String s) {
if (s.startsWith("\"") && s.endsWith("\"") && s.length() > 1) {
s = s.substring(1, s.length() - 1);
// check for any escaped characters
if (s.indexOf('\\') >= 0) {
StringBuilder sb = new StringBuilder(s.length()); // approx
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '\\' && i < s.length() - 1)
c = s.charAt(++i);
sb.append(c);
}
s = sb.toString();
}
}
return s;
}
/**
* The equality operator.
*/
@Override
public boolean equals(Object a) {
if (!(a instanceof InternetAddress))
return false;
String s = ((InternetAddress)a).getAddress();
if (s == address)
return true;
if (address != null && address.equalsIgnoreCase(s))
return true;
return false;
}
/**
* Compute a hash code for the address.
*/
@Override
public int hashCode() {
if (address == null)
return 0;
else
return address.toLowerCase(Locale.ENGLISH).hashCode();
}
/**
* Convert the given array of InternetAddress objects into
* a comma separated sequence of address strings. The
* resulting string contains only US-ASCII characters, and
* hence is mail-safe.
*
* @param addresses array of InternetAddress objects
* @exception ClassCastException if any address object in the
* given array is not an InternetAddress object. Note
* that this is a RuntimeException.
* @return comma separated string of addresses
*/
public static String toString(Address[] addresses) {
return toString(addresses, 0);
}
/**
* Convert the given array of InternetAddress objects into
* a comma separated sequence of address strings. The
* resulting string contains Unicode characters.
*
* @param addresses array of InternetAddress objects
* @exception ClassCastException if any address object in the
* given array is not an InternetAddress object. Note
* that this is a RuntimeException.
* @return comma separated string of addresses
* @since JavaMail 1.6
*/
public static String toUnicodeString(Address[] addresses) {
return toUnicodeString(addresses, 0);
}
/**
* Convert the given array of InternetAddress objects into
* a comma separated sequence of address strings. The
* resulting string contains only US-ASCII characters, and
* hence is mail-safe. <p>
*
* The 'used' parameter specifies the number of character positions
* already taken up in the field into which the resulting address
* sequence string is to be inserted. It is used to determine the
* line-break positions in the resulting address sequence string.
*
* @param addresses array of InternetAddress objects
* @param used number of character positions already used, in
* the field into which the address string is to
* be inserted.
* @exception ClassCastException if any address object in the
* given array is not an InternetAddress object. Note
* that this is a RuntimeException.
* @return comma separated string of addresses
*/
public static String toString(Address[] addresses, int used) {
if (addresses == null || addresses.length == 0)
return null;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < addresses.length; i++) {
if (i != 0) { // need to append comma
sb.append(", ");
used += 2;
}
// prefer not to split a single address across lines so used=0 below
String s = MimeUtility.fold(0, addresses[i].toString());
int len = lengthOfFirstSegment(s); // length till CRLF
if (used + len > 76) { // overflows ...
// smash trailing space from ", " above
int curlen = sb.length();
if (curlen > 0 && sb.charAt(curlen - 1) == ' ')
sb.setLength(curlen - 1);
sb.append("\r\n\t"); // .. start new continuation line
used = 8; // account for the starting <tab> char
}
sb.append(s);
used = lengthOfLastSegment(s, used);
}
return sb.toString();
}
/**
* Convert the given array of InternetAddress objects into
* a comma separated sequence of address strings. The
* resulting string contains Unicode characters. <p>
*
* The 'used' parameter specifies the number of character positions
* already taken up in the field into which the resulting address
* sequence string is to be inserted. It is used to determine the
* line-break positions in the resulting address sequence string.
*
* @param addresses array of InternetAddress objects
* @param used number of character positions already used, in
* the field into which the address string is to
* be inserted.
* @exception ClassCastException if any address object in the
* given array is not an InternetAddress object. Note
* that this is a RuntimeException.
* @return comma separated string of addresses
* @since JavaMail 1.6
*/
/*
* XXX - This is exactly the same as the above, except it uses
* toUnicodeString instead of toString.
* XXX - Since the line length restrictions are in bytes, not characters,
* we convert all non-ASCII addresses to UTF-8 byte strings,
* which we then convert to ISO-8859-1 Strings where every
* character respresents one UTF-8 byte. At the end we reverse
* the conversion to get back to a correct Unicode string.
* This is a hack to allow all the other character-based methods
* to work properly with UTF-8 bytes.
*/
public static String toUnicodeString(Address[] addresses, int used) {
if (addresses == null || addresses.length == 0)
return null;
StringBuilder sb = new StringBuilder();
boolean sawNonAscii = false;
for (int i = 0; i < addresses.length; i++) {
if (i != 0) { // need to append comma
sb.append(", ");
used += 2;
}
// prefer not to split a single address across lines so used=0 below
String as = ((InternetAddress)addresses[i]).toUnicodeString();
if (MimeUtility.checkAscii(as) != MimeUtility.ALL_ASCII) {
sawNonAscii = true;
as = new String(as.getBytes(StandardCharsets.UTF_8),
StandardCharsets.ISO_8859_1);
}
String s = MimeUtility.fold(0, as);
int len = lengthOfFirstSegment(s); // length till CRLF
if (used + len > 76) { // overflows ...
// smash trailing space from ", " above
int curlen = sb.length();
if (curlen > 0 && sb.charAt(curlen - 1) == ' ')
sb.setLength(curlen - 1);
sb.append("\r\n\t"); // .. start new continuation line
used = 8; // account for the starting <tab> char
}
sb.append(s);
used = lengthOfLastSegment(s, used);
}
String ret = sb.toString();
if (sawNonAscii)
ret = new String(ret.getBytes(StandardCharsets.ISO_8859_1),
StandardCharsets.UTF_8);
return ret;
}
/*
* Return the length of the first segment within this string.
* If no segments exist, the length of the whole line is returned.
*/
private static int lengthOfFirstSegment(String s) {
int pos;
if ((pos = s.indexOf("\r\n")) != -1)
return pos;
else
return s.length();
}
/*
* Return the length of the last segment within this string.
* If no segments exist, the length of the whole line plus
* <code>used</code> is returned.
*/
private static int lengthOfLastSegment(String s, int used) {
int pos;
if ((pos = s.lastIndexOf("\r\n")) != -1)
return s.length() - pos - 2;
else
return s.length() + used;
}
/**
* Return an InternetAddress object representing the current user.
* The entire email address may be specified in the "mail.from"
* property. If not set, the "mail.user" and "mail.host" properties
* are tried. If those are not set, the "user.name" property and
* <code>InetAddress.getLocalHost</code> method are tried.
* Security exceptions that may occur while accessing this information
* are ignored. If it is not possible to determine an email address,
* null is returned.
*
* @param session Session object used for property lookup
* @return current user's email address
*/
public static InternetAddress getLocalAddress(Session session) {
try {
return _getLocalAddress(session);
} catch (SecurityException sex) { // ignore it
} catch (AddressException ex) { // ignore it
} catch (UnknownHostException ex) { } // ignore it
return null;
}
/**
* A package-private version of getLocalAddress that doesn't swallow
* the exception. Used by MimeMessage.setFrom() to report the reason
* for the failure.
*/
// package-private
static InternetAddress _getLocalAddress(Session session)
throws SecurityException, AddressException, UnknownHostException {
String user = null, host = null, address = null;
if (session == null) {
user = System.getProperty("user.name");
host = getLocalHostName();
} else {
address = session.getProperty("mail.from");
if (address == null) {
user = session.getProperty("mail.user");
if (user == null || user.length() == 0)
user = session.getProperty("user.name");
if (user == null || user.length() == 0)
user = System.getProperty("user.name");
host = session.getProperty("mail.host");
if (host == null || host.length() == 0)
host = getLocalHostName();
}
}
if (address == null && user != null && user.length() != 0 &&
host != null && host.length() != 0)
address = MimeUtility.quote(user.trim(), specialsNoDot + "\t ") +
"@" + host;
if (address == null)
return null;
return new InternetAddress(address);
}
/**
* Get the local host name from InetAddress and return it in a form
* suitable for use in an email address.
*/
private static String getLocalHostName() throws UnknownHostException {
String host = null;
InetAddress me = InetAddress.getLocalHost();
if (me != null) {
// try canonical host name first
if (useCanonicalHostName)
host = me.getCanonicalHostName();
if (host == null)
host = me.getHostName();
// if we can't get our name, use local address literal
if (host == null)
host = me.getHostAddress();
if (host != null && host.length() > 0 && isInetAddressLiteral(host))
host = '[' + host + ']';
}
return host;
}
/**
* Is the address an IPv4 or IPv6 address literal, which needs to
* be enclosed in "[]" in an email address? IPv4 literals contain
* decimal digits and dots, IPv6 literals contain hex digits, dots,
* and colons. We're lazy and don't check the exact syntax, just
* the allowed characters; strings that have only the allowed
* characters in a literal but don't meet the syntax requirements
* for a literal definitely can't be a host name and thus will fail
* later when used as an address literal.
*/
private static boolean isInetAddressLiteral(String addr) {
boolean sawHex = false, sawColon = false;
for (int i = 0; i < addr.length(); i++) {
char c = addr.charAt(i);
if (c >= '0' && c <= '9')
; // digits always ok
else if (c == '.')
; // dot always ok
else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
sawHex = true; // need to see a colon too
else if (c == ':')
sawColon = true;
else
return false; // anything else, definitely not a literal
}
return !sawHex || sawColon;
}
/**
* Parse the given comma separated sequence of addresses into
* InternetAddress objects. Addresses must follow RFC822 syntax.
*
* @param addresslist comma separated address strings
* @return array of InternetAddress objects
* @exception AddressException if the parse failed
*/
public static InternetAddress[] parse(String addresslist)
throws AddressException {
return parse(addresslist, true);
}
/**
* Parse the given sequence of addresses into InternetAddress
* objects. If <code>strict</code> is false, simple email addresses
* separated by spaces are also allowed. If <code>strict</code> is
* true, many (but not all) of the RFC822 syntax rules are enforced.
* In particular, even if <code>strict</code> is true, addresses
* composed of simple names (with no "@domain" part) are allowed.
* Such "illegal" addresses are not uncommon in real messages. <p>
*
* Non-strict parsing is typically used when parsing a list of
* mail addresses entered by a human. Strict parsing is typically
* used when parsing address headers in mail messages.
*
* @param addresslist comma separated address strings
* @param strict enforce RFC822 syntax
* @return array of InternetAddress objects
* @exception AddressException if the parse failed
*/
public static InternetAddress[] parse(String addresslist, boolean strict)
throws AddressException {
return parse(addresslist, strict, false);
}
/**
* Parse the given sequence of addresses into InternetAddress
* objects. If <code>strict</code> is false, the full syntax rules for
* individual addresses are not enforced. If <code>strict</code> is
* true, many (but not all) of the RFC822 syntax rules are enforced. <p>
*
* To better support the range of "invalid" addresses seen in real
* messages, this method enforces fewer syntax rules than the
* <code>parse</code> method when the strict flag is false
* and enforces more rules when the strict flag is true. If the
* strict flag is false and the parse is successful in separating out an
* email address or addresses, the syntax of the addresses themselves
* is not checked.
*
* @param addresslist comma separated address strings
* @param strict enforce RFC822 syntax
* @return array of InternetAddress objects
* @exception AddressException if the parse failed
* @since JavaMail 1.3
*/
public static InternetAddress[] parseHeader(String addresslist,
boolean strict) throws AddressException {
return parse(MimeUtility.unfold(addresslist), strict, true);
}
/*
* RFC822 Address parser.
*
* XXX - This is complex enough that it ought to be a real parser,
* not this ad-hoc mess, and because of that, this is not perfect.
*
* XXX - Deal with encoded Headers too.
*/
@SuppressWarnings("fallthrough")
private static InternetAddress[] parse(String s, boolean strict,
boolean parseHdr) throws AddressException {
int start, end, index, nesting;
int start_personal = -1, end_personal = -1;
int length = s.length();
boolean ignoreErrors = parseHdr && !strict;
boolean in_group = false; // we're processing a group term
boolean route_addr = false; // address came from route-addr term
boolean rfc822 = false; // looks like an RFC822 address
char c;
List<InternetAddress> v = new ArrayList<>();
InternetAddress ma;
for (start = end = -1, index = 0; index < length; index++) {
c = s.charAt(index);
switch (c) {
case '(': // We are parsing a Comment. Ignore everything inside.
// XXX - comment fields should be parsed as whitespace,
// more than one allowed per address
rfc822 = true;
if (start >= 0 && end == -1)
end = index;
int pindex = index;
for (index++, nesting = 1; index < length && nesting > 0;
index++) {
c = s.charAt(index);
switch (c) {
case '\\':
index++; // skip both '\' and the escaped char
break;
case '(':
nesting++;
break;
case ')':
nesting--;
break;
default:
break;
}
}
if (nesting > 0) {
if (!ignoreErrors)
throw new AddressException("Missing ')'", s, index);
// pretend the first paren was a regular character and
// continue parsing after it
index = pindex + 1;
break;
}
index--; // point to closing paren
if (start_personal == -1)
start_personal = pindex + 1;
if (end_personal == -1)
end_personal = index;
break;
case ')':
if (!ignoreErrors)
throw new AddressException("Missing '('", s, index);
// pretend the left paren was a regular character and
// continue parsing
if (start == -1)
start = index;
break;
case '<':
rfc822 = true;
if (route_addr) {
if (!ignoreErrors)
throw new AddressException(
"Extra route-addr", s, index);
// assume missing comma between addresses
if (start == -1) {
route_addr = false;
rfc822 = false;
start = end = -1;
break; // nope, nothing there
}
if (!in_group) {
// got a token, add this to our InternetAddress list
if (end == -1) // should never happen
end = index;
String addr = s.substring(start, end).trim();
ma = new InternetAddress();
ma.setAddress(addr);
if (start_personal >= 0) {
ma.encodedPersonal = unquote(
s.substring(start_personal, end_personal).
trim());
}
v.add(ma);
route_addr = false;
rfc822 = false;
start = end = -1;
start_personal = end_personal = -1;
// continue processing this new address...
}
}
int rindex = index;
boolean inquote = false;
outf:
for (index++; index < length; index++) {
c = s.charAt(index);
switch (c) {
case '\\': // XXX - is this needed?
index++; // skip both '\' and the escaped char
break;
case '"':
inquote = !inquote;
break;
case '>':
if (inquote)
continue;
break outf; // out of for loop
default:
break;
}
}
// did we find a matching quote?
if (inquote) {
if (!ignoreErrors)
throw new AddressException("Missing '\"'", s, index);
// didn't find matching quote, try again ignoring quotes
// (e.g., ``<"@foo.com>'')
outq:
for (index = rindex + 1; index < length; index++) {
c = s.charAt(index);
if (c == '\\') // XXX - is this needed?
index++; // skip both '\' and the escaped char
else if (c == '>')
break;
}
}
// did we find a terminating '>'?
if (index >= length) {
if (!ignoreErrors)
throw new AddressException("Missing '>'", s, index);
// pretend the "<" was a regular character and
// continue parsing after it (e.g., ``<@foo.com'')
index = rindex + 1;
if (start == -1)
start = rindex; // back up to include "<"
break;
}
if (!in_group) {
if (start >= 0) {
// seen some characters? use them as the personal name
start_personal = start;
end_personal = rindex;
}
start = rindex + 1;
}
route_addr = true;
end = index;
break;
case '>':
if (!ignoreErrors)
throw new AddressException("Missing '<'", s, index);
// pretend the ">" was a regular character and
// continue parsing (e.g., ``>@foo.com'')
if (start == -1)
start = index;
break;
case '"': // parse quoted string
int qindex = index;
rfc822 = true;
if (start == -1)
start = index;
outq:
for (index++; index < length; index++) {
c = s.charAt(index);
switch (c) {
case '\\':
index++; // skip both '\' and the escaped char
break;
case '"':
break outq; // out of for loop
default:
break;
}
}
if (index >= length) {
if (!ignoreErrors)
throw new AddressException("Missing '\"'", s, index);
// pretend the quote was a regular character and
// continue parsing after it (e.g., ``"@foo.com'')
index = qindex + 1;
}
break;
case '[': // a domain-literal, probably
int lindex = index;
rfc822 = true;
if (start == -1)
start = index;
outb:
for (index++; index < length; index++) {
c = s.charAt(index);
switch (c) {
case '\\':
index++; // skip both '\' and the escaped char
break;
case ']':
break outb; // out of for loop
default:
break;
}
}
if (index >= length) {
if (!ignoreErrors)
throw new AddressException("Missing ']'", s, index);
// pretend the "[" was a regular character and
// continue parsing after it (e.g., ``[@foo.com'')
index = lindex + 1;
}
break;
case ';':
if (start == -1) {
route_addr = false;
rfc822 = false;
start = end = -1;
break; // nope, nothing there
}
if (in_group) {