Compare commits
1111 Commits
Author | SHA1 | Date | |
---|---|---|---|
54f9fd36a7 | |||
|
8de2ec7f27 | ||
|
ecd5239c2b | ||
|
9050ef16a2 | ||
|
fde7b985fc | ||
|
53c87ff974 | ||
|
d8aef84127 | ||
|
2f7afa8288 | ||
|
ecb6893e6c | ||
|
e935feb068 | ||
|
32cc97207c | ||
|
8494d9942d | ||
|
67d2db7c85 | ||
|
54d30833fc | ||
|
d08942cc50 | ||
|
7959033f26 | ||
|
07c12e83d0 | ||
|
a489cc3771 | ||
|
d301d63274 | ||
|
17a2309bb9 | ||
|
9d44f0e062 | ||
|
d16d95d840 | ||
|
777ace3420 | ||
|
05f4002a2a | ||
|
ee7a95b750 | ||
|
366531bdf7 | ||
|
7fc8767a5c | ||
|
591785a3ab | ||
|
2d45e53739 | ||
|
e01b1b189f | ||
|
2fdf076d4e | ||
|
3c1c1e4e58 | ||
|
270d22681f | ||
|
f0962fdb6a | ||
|
37a313efb5 | ||
|
586fc314a1 | ||
|
4fb12ff12b | ||
|
a63a91fa54 | ||
|
6138afb579 | ||
|
1cd7df1369 | ||
|
706fd85a04 | ||
|
916929e507 | ||
|
9c258ee60c | ||
|
a24d85d754 | ||
|
4b273c1749 | ||
|
0f66cacf10 | ||
|
b45065a5b2 | ||
|
f96ffdcd4e | ||
|
b660d45b6c | ||
|
9d225dc84c | ||
|
0b49c85c93 | ||
|
4784a3f6ff | ||
|
c4723cba4a | ||
|
ce86e773e0 | ||
|
3e833580ac | ||
|
05934d75d8 | ||
|
64e22a72ed | ||
|
0f848ee51f | ||
|
1dfc2a5490 | ||
|
c37934ea16 | ||
|
d538278be6 | ||
|
83bb97b0c5 | ||
|
017ae1d2f3 | ||
|
babd3a530f | ||
|
ba8cb6c85d | ||
|
09babb6e88 | ||
|
8bcf9b1d50 | ||
|
80fa468ec2 | ||
|
24b61e0743 | ||
|
a3375d7030 | ||
|
9275bb2943 | ||
|
af36129449 | ||
|
632517be81 | ||
|
79b06cd0cc | ||
|
1ec2c5b095 | ||
|
6a03e62f52 | ||
|
46f74bd11c | ||
|
ffc5ba2cf3 | ||
|
ebcd10d1b1 | ||
|
13f6b42250 | ||
|
3e84c20c9b | ||
|
16f09611fe | ||
|
dc95f5feab | ||
|
05574b0f16 | ||
|
3762a7bc69 | ||
|
bf0333ba31 | ||
|
ab964cf8af | ||
|
54831fba73 | ||
|
e4cfd3c886 | ||
|
f0e1b14b58 | ||
|
494b16196d | ||
|
5f14e3b4e1 | ||
|
f733cc38ba | ||
|
00528f5d24 | ||
|
855da35f3a | ||
|
d528864a25 | ||
|
83de921a1a | ||
|
c147ce0822 | ||
|
672a85bcf4 | ||
|
e1bd260bd0 | ||
|
08beb212c2 | ||
|
42419fc4e5 | ||
|
20a1fa384c | ||
|
9f2bbe9ae4 | ||
|
e0a249098b | ||
|
8e0a30c5f2 | ||
|
110400a85b | ||
|
520e327775 | ||
|
d0cd7c368d | ||
|
810d0cf6b4 | ||
|
147db8cc5e | ||
|
2f832e5fa4 | ||
|
46bac187d5 | ||
|
67404b1883 | ||
|
abd2b20850 | ||
|
29f7552c3a | ||
|
b826d4e98d | ||
|
bf344dee5d | ||
|
2a404b30d2 | ||
|
cb0a99281a | ||
|
bc284584d1 | ||
|
5330fe5b27 | ||
|
d0fa82269f | ||
|
dd20ff5aa3 | ||
|
737e0d2ac8 | ||
|
4db7de4ed8 | ||
|
d443a6d4eb | ||
|
a979accb54 | ||
|
af491fdb41 | ||
|
084e7a1687 | ||
|
8fbb3edd1c | ||
|
5035e7e3d1 | ||
|
23c01b0390 | ||
|
a656a61c65 | ||
|
d5d42469b0 | ||
|
492d65feed | ||
|
e7f706b78d | ||
|
bcb668300f | ||
|
6def0be158 | ||
|
fd99c279e5 | ||
|
ab877453d9 | ||
|
d61ac959a9 | ||
|
ae258f5761 | ||
|
b40749547c | ||
|
e5e4c29736 | ||
|
6f3f555986 | ||
|
c6abb50d10 | ||
|
1a706c3113 | ||
|
583a637d79 | ||
|
d8448c3510 | ||
|
ab8746ffe9 | ||
|
132ede425b | ||
|
cac1f1ca0d | ||
|
160b9eb354 | ||
|
f887348953 | ||
|
e15cda8504 | ||
|
68147880ce | ||
|
c64ae008c1 | ||
|
6b52f41e2c | ||
|
e16f8af667 | ||
|
15cb58fe42 | ||
|
9659bee8c5 | ||
|
ebef8eccb9 | ||
|
ffb4507776 | ||
|
24e6b39dc0 | ||
|
461778ed11 | ||
|
1301645387 | ||
|
ebeed31705 | ||
|
d703ac9148 | ||
|
9f1ecf7220 | ||
|
b4900cc6af | ||
|
62c2894fce | ||
|
80f8e4a81d | ||
|
0d0b80f142 | ||
|
c4e202ecd9 | ||
|
9bf546b33b | ||
|
f5cac2c71f | ||
|
63047e0ac6 | ||
|
980d799087 | ||
|
9b1a0b3614 | ||
|
aae71125c6 | ||
|
9df1a3ee80 | ||
|
ba1fc1305f | ||
|
9c3cab2354 | ||
|
8d510e96a9 | ||
|
5013f36ba4 | ||
|
9ba2725ab1 | ||
|
e0abcc3f67 | ||
|
5d3cdc2724 | ||
|
bb3f84fda6 | ||
|
d301efea58 | ||
|
3a527cbcf6 | ||
|
948cb971ad | ||
|
5c036e2991 | ||
|
26eb1f52e5 | ||
|
b515e947cf | ||
|
ee1180e34c | ||
|
cadac6dd89 | ||
|
49c4115e46 | ||
|
3377e50352 | ||
|
fce12b2450 | ||
|
1a20ca06f1 | ||
|
80221dace8 | ||
|
1f27897679 | ||
|
5175ff9df4 | ||
|
9814442de4 | ||
|
36abde2c0b | ||
|
937ca7e17a | ||
|
d7da286098 | ||
|
0241001c63 | ||
|
19db6c703b | ||
|
474efa1831 | ||
|
dc8fd39c7e | ||
|
4bec165fdc | ||
|
de8da4dab4 | ||
|
e8c591e6be | ||
|
0374dc9cb1 | ||
|
a0b4faf688 | ||
|
d57f6c0ed5 | ||
|
7158abe7ff | ||
|
a00a119e18 | ||
|
3077e6a2d7 | ||
|
0a07250417 | ||
|
d678ccc160 | ||
|
00b7b74878 | ||
|
712acf4481 | ||
|
38d3564c57 | ||
|
41ac5a9fed | ||
|
edf75a32d8 | ||
|
8627e65cab | ||
|
fed15a01e5 | ||
|
033c1502db | ||
|
a51b608e5e | ||
|
828a580eb8 | ||
|
4075b72b72 | ||
|
fbfa6d146f | ||
|
ba79779758 | ||
|
9e47686277 | ||
|
7b67d054a4 | ||
|
1046308a38 | ||
|
4bc9d94831 | ||
|
bcd570f884 | ||
|
4827b4c437 | ||
|
7f811fce2c | ||
|
6f156498ed | ||
|
bb83fdc0e8 | ||
|
fdc597aadf | ||
|
12cf19b63e | ||
|
bc2fe2dbfe | ||
|
d112344780 | ||
|
28e9c2a8ec | ||
|
d46d355f69 | ||
|
cced35b3b8 | ||
|
e513af9529 | ||
|
b781ace4fa | ||
|
4c78d12fc6 | ||
|
e3fef1af98 | ||
|
581d32acd6 | ||
|
445c978f31 | ||
|
29ad0f0f99 | ||
|
40b6228756 | ||
|
d92be22ce3 | ||
|
74820a40db | ||
|
74fdbb7859 | ||
|
8f7f656355 | ||
|
0e03f262b3 | ||
|
378acbd313 | ||
|
977d15c190 | ||
|
6825eafb87 | ||
|
564e2432e1 | ||
|
98bdf54672 | ||
|
74d09943c0 | ||
|
d2d85393d3 | ||
|
5e4743bf66 | ||
|
fe7b88f7c2 | ||
|
64e92ab1c1 | ||
|
c9b2ec533c | ||
|
23c9398c03 | ||
|
395b70fa22 | ||
|
da51bdf1b3 | ||
|
a7b16c1210 | ||
|
9363c5b276 | ||
|
585d9cbe7f | ||
|
1e628e7177 | ||
|
658657447e | ||
|
de2eb25446 | ||
|
cb94b5b192 | ||
|
ac365567ee | ||
|
41bd420213 | ||
|
8fce9e3654 | ||
|
087238f507 | ||
|
2e05127c97 | ||
|
ecd316d0af | ||
|
9523a589fc | ||
|
c79256684d | ||
|
dfb025033d | ||
|
4808406739 | ||
|
833d9c5de8 | ||
|
79b03b5e4f | ||
|
4e964e271c | ||
|
0153766dd5 | ||
|
0f312f012e | ||
|
055d4104b7 | ||
|
a15583a080 | ||
|
de4b6d1076 | ||
|
bdbe976396 | ||
|
e98f323222 | ||
|
f7da704007 | ||
|
78ed2a23b1 | ||
|
60070b7883 | ||
|
111212b391 | ||
|
7958467503 | ||
|
b481d3f978 | ||
|
787c014265 | ||
|
1bf159a300 | ||
|
bcd64017e3 | ||
|
4db57dfc85 | ||
|
63abf05776 | ||
|
2532362ed5 | ||
|
bd97004ebd | ||
|
743e640d8c | ||
|
ce862c88f8 | ||
|
e5f0bec6bc | ||
|
c5ba202a56 | ||
|
34b5d56ab1 | ||
|
1a5ecfea1d | ||
|
30e37000f9 | ||
|
d7edb0ed4f | ||
|
c7229e4724 | ||
|
523ebd0f2a | ||
|
3eb25a011f | ||
|
d7085a2f07 | ||
|
abbad18283 | ||
|
04b5b4a230 | ||
|
4bc003e173 | ||
|
7b5c73b43c | ||
|
ddd78bd3e3 | ||
|
72f022d7ed | ||
|
57ad0fd6b3 | ||
|
ad8da49991 | ||
|
b6315b15b0 | ||
|
6804ee04e3 | ||
|
65d2de0fcc | ||
|
928c7f33a3 | ||
|
e45d780c6f | ||
|
b69bba01da | ||
|
e2a9dd3042 | ||
|
82736f3a8b | ||
|
78758714c2 | ||
|
e8c6a56fd2 | ||
|
fe8e779b32 | ||
|
704cb35d7e | ||
|
c96a11212e | ||
|
b0d401c3b7 | ||
|
6c172f94a1 | ||
|
152e0a0530 | ||
|
703c007fc8 | ||
|
8194c20ffe | ||
|
c473ddc90a | ||
|
4f8fc5bc5b | ||
|
7752f42db6 | ||
|
ae6f1fa299 | ||
|
1bd74ad263 | ||
|
52b3974c4f | ||
|
105bca735b | ||
|
6a24aca343 | ||
|
9e7721ca62 | ||
|
348051cb95 | ||
|
4d61a6407d | ||
|
0a6046cae7 | ||
|
7d3cea87f9 | ||
|
1212f9d0f2 | ||
|
cfbebdb4b6 | ||
|
2eecb2d2c5 | ||
|
6ed52ac551 | ||
|
e214dbbd99 | ||
|
98c1935c85 | ||
|
4b0d016bb7 | ||
|
2011655344 | ||
|
df8a823e41 | ||
|
3760ca95d5 | ||
|
3f7fc83d58 | ||
|
fd02085946 | ||
|
043df7e7c5 | ||
|
714acabf83 | ||
|
66dd4990b1 | ||
|
0a6920c63e | ||
|
6a1fee90ee | ||
|
0f476978ce | ||
|
3c38cb2d7f | ||
|
e1fe6a97e1 | ||
|
51bc464449 | ||
|
9c7776d289 | ||
|
2f30b3956d | ||
|
b36c788ce0 | ||
|
16f8a3ef14 | ||
|
27e0c75021 | ||
|
ac33de6310 | ||
|
7c79e7c6b5 | ||
|
cc6c6bf096 | ||
|
5248350953 | ||
|
b091ae4fa0 | ||
|
e447257414 | ||
|
328405419a | ||
|
86487a738d | ||
|
deb11b2226 | ||
|
231684936b | ||
|
245a6330ed | ||
|
44f6a2479b | ||
|
946565347a | ||
|
d24998d584 | ||
|
62c5ac8e5f | ||
|
23d9310c61 | ||
|
c608258494 | ||
|
15a4c90f27 | ||
|
b443af43ae | ||
|
2a2e18e8b6 | ||
|
36ef6df018 | ||
|
7d6e6b8abe | ||
|
476cb1d4ce | ||
|
238c1650c5 | ||
|
8ef9eae0d6 | ||
|
bd697bb56d | ||
|
96413a50a3 | ||
|
209072398e | ||
|
ff70bf4e22 | ||
|
39fc962da0 | ||
|
12248bca92 | ||
|
54d62eb7b9 | ||
|
b644194a3d | ||
|
40041ac0e0 | ||
|
2e98ff56e5 | ||
|
0024f39bc6 | ||
|
708fb57c04 | ||
|
6264527abc | ||
|
9f16b9f465 | ||
|
2536719749 | ||
|
5af649c271 | ||
|
9fd722d7cd | ||
|
6f4610dd5b | ||
|
edfd20bf85 | ||
|
e59adf46c0 | ||
|
12291bceb5 | ||
|
ba4dd24bd5 | ||
|
f89b0548a6 | ||
|
ca10e4d94a | ||
|
987b8b17b1 | ||
|
37b0666f4a | ||
|
f94491a359 | ||
|
d1d9f1ea19 | ||
|
0ce7c911e5 | ||
|
e374538110 | ||
|
3919c9d2d6 | ||
|
d1d7b60a09 | ||
|
f7d3eaa006 | ||
|
9f4f0cf6a8 | ||
|
d32d6eed0e | ||
|
51a60b5ad3 | ||
|
2404b80b04 | ||
|
bcb6c75c2e | ||
|
8630bb0ad4 | ||
|
825c508ff6 | ||
|
68c95d0283 | ||
|
878189baec | ||
|
272a4bc1cf | ||
|
7cbea6e4b2 | ||
|
3b2d625a09 | ||
|
410edd7107 | ||
|
7177afa4d2 | ||
|
105948d78c | ||
|
14edb093f2 | ||
|
1fa6e117e1 | ||
|
b51ad495ed | ||
|
aaf3963567 | ||
|
54d6566fb5 | ||
|
2c81495aea | ||
|
68cee3e9a3 | ||
|
98b5d63909 | ||
|
854b1b3ffc | ||
|
552e552e88 | ||
|
56c30095e2 | ||
|
d9b6e10cbe | ||
|
a725099693 | ||
|
526fa443a8 | ||
|
6542ba3a72 | ||
|
c252335d2e | ||
|
9b61fe0f0e | ||
|
9e6fc7c7d1 | ||
|
0f70d5db40 | ||
|
bd4a236525 | ||
|
7211080415 | ||
|
87ca0d3d2a | ||
|
34cfd8e5b4 | ||
|
e64ca84f1b | ||
|
c3f1420ef6 | ||
|
dfd40659d1 | ||
|
6a1905b7b7 | ||
|
44ecf5d588 | ||
|
f87ab53b9b | ||
|
11a9eff109 | ||
|
6cb3c991db | ||
|
3c4ad91614 | ||
|
d3073be89a | ||
|
83d876f246 | ||
|
f6822c973d | ||
|
1e89314f3e | ||
|
b3bc85ba10 | ||
|
ca88f59c05 | ||
|
4299eb9771 | ||
|
4a6c52947d | ||
|
0ced8746af | ||
|
33d12e4169 | ||
|
187d760e5f | ||
|
dfe1771fcb | ||
|
b7a8c9b707 | ||
|
3dab8a2ad1 | ||
|
438a350f55 | ||
|
a9b0907c31 | ||
|
ba26cfce90 | ||
|
5dc1b82340 | ||
|
aa959f4457 | ||
|
e592aff437 | ||
|
d980e49fd1 | ||
|
c6df8f1ba1 | ||
|
dd1ec5f47b | ||
|
668ee71b6c | ||
|
4aad31e05a | ||
|
d2bfcab939 | ||
|
c3c8221d4a | ||
|
7945aab8a7 | ||
|
0f844fd4d2 | ||
|
270160e65a | ||
|
87acbb7cac | ||
|
06d1a2471a | ||
|
a141457886 | ||
|
40102d560d | ||
|
195f28db00 | ||
|
444756839c | ||
|
fe7c0ebfac | ||
|
159017e91d | ||
|
d38f21265d | ||
|
02c0b5f2a3 | ||
|
203dcfe2c3 | ||
|
dadf5e0865 | ||
|
010d8c9f7e | ||
|
b8870493cd | ||
|
2226ae6a8e | ||
|
c438bc1222 | ||
|
afb65d5ad7 | ||
|
46d083bcad | ||
|
2be10febf9 | ||
|
547eb74774 | ||
|
b6079d6460 | ||
|
3b89c6a139 | ||
|
f8ffead008 | ||
|
8cfd73f6b7 | ||
|
ff72a63e17 | ||
|
352fb8fd25 | ||
|
99991e6651 | ||
|
4105cdd3cb | ||
|
082dd953b2 | ||
|
9d93735d27 | ||
|
64cd587b5e | ||
|
3612d182a1 | ||
|
960d7ba026 | ||
|
1ea34d2378 | ||
|
be954d729c | ||
|
7d32b3d462 | ||
|
a9aa4645af | ||
|
545dd0db06 | ||
|
5513d5a99b | ||
|
d467dca32c | ||
|
601e2880ac | ||
|
bd839a995f | ||
|
a6fc06f7f9 | ||
|
3e4beae631 | ||
|
b31660c63e | ||
|
471f1df160 | ||
|
98559900c2 | ||
|
87a9126107 | ||
|
a2a9e751e4 | ||
|
9dba60c997 | ||
|
eced036d69 | ||
|
91ef5fa816 | ||
|
89ba2c510b | ||
|
f92da3af59 | ||
|
038fceabf0 | ||
|
939b2e3520 | ||
|
5ddfe4b58e | ||
|
47cedef85c | ||
|
0f6719387c | ||
|
a10b9ae452 | ||
|
59a61366ca | ||
|
094feced0c | ||
|
2e318096b7 | ||
|
9911208441 | ||
|
5a6648ceeb | ||
|
9e203b75cc | ||
|
43c38a047f | ||
|
7dfbd906c9 | ||
|
6f14294164 | ||
|
5f0f4e9c21 | ||
|
f5cfaceef4 | ||
|
5802e6a36a | ||
|
7edd1cb53c | ||
|
8ef45e8f9a | ||
|
bd9efa8d01 | ||
|
cb67a21a93 | ||
|
ae8dcc5e8a | ||
|
0c38d4f169 | ||
|
759fa77c9a | ||
|
7c4a684f86 | ||
|
9728609c4c | ||
|
8ade424270 | ||
|
a756fa3683 | ||
|
348fb4dceb | ||
|
c80634d501 | ||
|
c2db88d960 | ||
|
1282e9d461 | ||
|
0993e5c57e | ||
|
ac1ed9eef3 | ||
|
05d8fb5e42 | ||
|
2a1733564e | ||
|
e4d26b8c75 | ||
|
bc60c860b8 | ||
|
786511ed88 | ||
|
028f6f9055 | ||
|
0fba273357 | ||
|
8166f03e87 | ||
|
af77bbd1bc | ||
|
c472b89d23 | ||
|
1783dd1a63 | ||
|
b39f9b95f1 | ||
|
f7fb0cca41 | ||
|
3bd9e7edf1 | ||
|
a495627d72 | ||
|
a659393326 | ||
|
ac08f520ae | ||
|
4ce2a56b0c | ||
|
c881207295 | ||
|
c8f6c4d625 | ||
|
21237c3720 | ||
|
2b05f90d4d | ||
|
c5085be2ca | ||
|
346d903ec3 | ||
|
5d5fab3081 | ||
|
cf3561da5c | ||
|
301ac48a38 | ||
|
b557ba008c | ||
|
c0be0eea12 | ||
|
fe033e014f | ||
|
65144e3759 | ||
|
231f3645f9 | ||
|
21cc3d9176 | ||
|
fa853f7e1d | ||
|
b10b13b865 | ||
|
0224a89109 | ||
|
88016ae52e | ||
|
38e9af5320 | ||
|
acab554ee5 | ||
|
2e981e0c7d | ||
|
c861b27df8 | ||
|
373c7569ed | ||
|
6d8497a3c3 | ||
|
bef10812d3 | ||
|
eb68bc0a9d | ||
|
34fd6d3ea7 | ||
|
ada74db8d5 | ||
|
51829a2451 | ||
|
7aa4c1308e | ||
|
41570e4305 | ||
|
fe49a5f005 | ||
|
710e7a4430 | ||
|
c36d2d7a5e | ||
|
96be55a262 | ||
|
f463aa9fa0 | ||
|
a671e51052 | ||
|
2486caaf2d | ||
|
14032088db | ||
|
3c025379d4 | ||
|
008891a375 | ||
|
0a507463c4 | ||
|
acd756e642 | ||
|
cf718780f6 | ||
|
3142a9a225 | ||
|
731da2747e | ||
|
750e4a7808 | ||
|
ba69b3a647 | ||
|
103f85aa01 | ||
|
324db56569 | ||
|
59bda357e0 | ||
|
d67a59b77f | ||
|
aad171ff7e | ||
|
38c90146e1 | ||
|
87802a01ef | ||
|
7ac7fe2cfe | ||
|
2fdcb77c5c | ||
|
082e13df26 | ||
|
95f33c38fe | ||
|
ed3fcf375a | ||
|
00a60a0f4f | ||
|
523180020c | ||
|
58efee8be2 | ||
|
5cc4d7c6a9 | ||
|
f17b45c152 | ||
|
e0ce5fc4ce | ||
|
e2cffa074f | ||
|
a9cfa9ae68 | ||
|
3a02bfb0a9 | ||
|
9c5b9cce90 | ||
|
0a09060ed7 | ||
|
215ada2e77 | ||
|
4260dc75d3 | ||
|
68850b1dc9 | ||
|
71e766999c | ||
|
e163fb87e1 | ||
|
00fb83e1b3 | ||
|
7fd52a735d | ||
|
421879e9cf | ||
|
56a48476e4 | ||
|
a08687b70e | ||
|
6155a65f65 | ||
|
9a99c77653 | ||
|
59ae1d034c | ||
|
615a1ae9a7 | ||
|
51aa34d52b | ||
|
da74253f7b | ||
|
818aed5f8c | ||
|
e1d9c60b83 | ||
|
79ae191c2e | ||
|
8e078bc014 | ||
|
b765988423 | ||
|
5640dece0f | ||
|
3054ff757b | ||
|
d6a9b4e4d4 | ||
|
c597d63ae6 | ||
|
6924d68376 | ||
|
6267f1249b | ||
|
420e6a91be | ||
|
1b14472a59 | ||
|
a7e157eac7 | ||
|
d5c6d96112 | ||
|
c4d930f326 | ||
|
24cdf811e6 | ||
|
eaa12d2bd4 | ||
|
13bc441c7b | ||
|
95cc319101 | ||
|
510195bce7 | ||
|
efc5565b91 | ||
|
ebed217c13 | ||
|
5c59b25367 | ||
|
7e040ea84c | ||
|
3da2ef7fbe | ||
|
8b93d37b39 | ||
|
136bdbc483 | ||
|
0526ddd2aa | ||
|
114d72da3c | ||
|
ab72aa0dd7 | ||
|
c53973910f | ||
|
19b808b93b | ||
|
4df53080d3 | ||
|
945e539341 | ||
|
857c72d691 | ||
|
088549ab62 | ||
|
6a6e9979e2 | ||
|
e0065ce014 | ||
|
16df038157 | ||
|
929a61c035 | ||
|
c36ef88e64 | ||
|
16ec0337d1 | ||
|
49dbaf034c | ||
|
cc8353d255 | ||
|
6175c4c72d | ||
|
71a8ffc2b5 | ||
|
3fb9cddb33 | ||
|
974a73b07d | ||
|
8f1723a451 | ||
|
5c93f105ea | ||
|
bd4b7d3664 | ||
|
95f62785fc | ||
|
7e3ae3ca3d | ||
|
b17890251d | ||
|
b490773546 | ||
|
5162d847ad | ||
|
01d2247ffd | ||
|
dc920b8641 | ||
|
e475e51731 | ||
|
a7898fa2eb | ||
|
18da76f4aa | ||
|
c2abfbe165 | ||
|
e55feee952 | ||
|
fba406c29f | ||
|
862d1267a8 | ||
|
db62215eca | ||
|
949f8ae47e | ||
|
902bd96f4a | ||
|
c6f0a5897b | ||
|
ef8fbb427e | ||
|
ab9968e71f | ||
|
bc4218be55 | ||
|
567ba9c712 | ||
|
3ca03e8a9d | ||
|
ff5edf43d4 | ||
|
617123c58b | ||
|
75fc76773d | ||
|
0a63466704 | ||
|
0f81cc192a | ||
|
6881daae6a | ||
|
f5b1ed920b | ||
|
094318dacb | ||
|
df3eef0052 | ||
|
c8150a12fa | ||
|
d67c054d4d | ||
|
9dc5338501 | ||
|
14a0a7a2a7 | ||
|
daea7f1ecd | ||
|
39590d49bd | ||
|
8d0901a178 | ||
|
0f991b434e | ||
|
0509e1541c | ||
|
4a69ef1509 | ||
|
47e09c92ea | ||
|
bf9264dbb0 | ||
|
fa7118dab3 | ||
|
abc765e893 | ||
|
4792cfa91d | ||
|
8bfd6ca3e0 | ||
|
c17d0323f2 | ||
|
cfeed40781 | ||
|
6075addfd0 | ||
|
2df205874e | ||
|
be2b3b1ec2 | ||
|
540de158a0 | ||
|
f7d397ea09 | ||
|
23f8d53178 | ||
|
c7e46faf0a | ||
|
90fedf7125 | ||
|
dc9720ca13 | ||
|
f24ac67e4d | ||
|
64fd04ece2 | ||
|
26491676fa | ||
|
3877f58515 | ||
|
6f49ebd975 | ||
|
1d1b14da21 | ||
|
871ee1cc6c | ||
|
1091e7af99 | ||
|
1f5e1660a8 | ||
|
fec864a426 | ||
|
dd4cbb7d3c | ||
|
8eb68c416a | ||
|
dd5ac0f1c4 | ||
|
1be9272b9b | ||
|
70399829c2 | ||
|
fab31983ab | ||
|
f89544ea8b | ||
|
ab3044c9fa | ||
|
cbbd0bc405 | ||
|
df75853c64 | ||
|
3d327763b5 | ||
|
e75dd7df39 | ||
|
8627a3e702 | ||
|
ac1e68af78 | ||
|
572427cb57 | ||
|
7ae3a15d7c | ||
|
21fe3f6cd2 | ||
|
1bc3271de3 | ||
|
1202f5109a | ||
|
e8d3553b2c | ||
|
8ac942d828 | ||
|
b942bf2889 | ||
|
309a4d6107 | ||
|
28398dbdad | ||
|
0c7740ce86 | ||
|
92e9e6d140 | ||
|
7701d4ddb2 | ||
|
53eac39a90 | ||
|
f1682fe517 | ||
|
90f63ba057 | ||
|
3527930f89 | ||
|
d419fab789 | ||
|
aec1a38578 | ||
|
32e1689255 | ||
|
cbddf960b1 | ||
|
94301e7e3b | ||
|
0664029a0f | ||
|
cf228583bc | ||
|
6e7fa0df03 | ||
|
f224483df4 | ||
|
67abde055a | ||
|
a62f7aaa29 | ||
|
abb3318846 | ||
|
b299713807 | ||
|
1a16dbf295 | ||
|
f24c298791 | ||
|
3349c51bcd | ||
|
6c7bac5851 | ||
|
d7b987f072 | ||
|
5e7a9a8489 | ||
|
4214c7290e | ||
|
094156cc2a | ||
|
ecb4ed41ba | ||
|
168ac641e0 | ||
|
e91e3f4e7d | ||
|
4955e34886 | ||
|
94296ec7df | ||
|
54faf52f16 | ||
|
2a092deb19 | ||
|
63e48f7e29 | ||
|
16b0797dc1 | ||
|
94cafccec2 | ||
|
8f2e88d42b | ||
|
06ec852090 | ||
|
9ca4585778 | ||
|
52d0460237 | ||
|
03925fb409 | ||
|
0de72c31b4 | ||
|
88010be316 | ||
|
84f679ebc2 | ||
|
ce77a4e48a | ||
|
043043d562 | ||
|
bb852bce8a | ||
|
c55a216433 | ||
|
87689120a5 | ||
|
019555587b | ||
|
3204643e26 | ||
|
db1ebe5481 | ||
|
9872bdb3ae | ||
|
2b32f7a6db | ||
|
aaa1bdd115 | ||
|
79a5bc9c7e | ||
|
38e3aa7db1 | ||
|
453f10128c | ||
|
78428e287b | ||
|
8832303c76 | ||
|
75fc5f0d70 | ||
|
18d80c8692 | ||
|
4fe3ed6692 | ||
|
388ee8e27d | ||
|
b06e912b20 | ||
|
d4017d24fe | ||
|
c1a77181bc | ||
|
211e989472 | ||
|
8e422f1183 | ||
|
43bf41332d | ||
|
25147754e9 | ||
|
4cdbe00732 | ||
|
d0a72b4f17 | ||
|
85e55e5408 | ||
|
9b807c325d | ||
|
dab8d3807f | ||
|
3fd7470d68 | ||
|
448a80e96a | ||
|
cedcd7e47c | ||
|
9333f4f7f4 | ||
|
765b390eb5 | ||
|
2a9ac867b9 | ||
|
5bf27c1031 | ||
|
9e6abf5fa7 | ||
|
0f39a9d5ba | ||
|
8eef43c282 | ||
|
40404c3700 | ||
|
76605f7d86 | ||
|
5f38306a9a | ||
|
eb13691918 | ||
|
4b57d79acf | ||
|
8368ba8a11 | ||
|
c5c195d243 | ||
|
48d11fd386 | ||
|
a4440b4042 | ||
|
b500047e42 | ||
|
0eb24512b0 | ||
|
f4ebc098c4 | ||
|
042bd70290 | ||
|
febf9b2fb3 | ||
|
9fee3e50c8 | ||
|
2a77aa1e41 | ||
|
9ea241f897 | ||
|
b4c4dea527 | ||
|
37272fbaf4 | ||
|
1bfb78ee51 | ||
|
d4246a0976 | ||
|
b398c4d7f4 | ||
|
9fe71bca2d | ||
|
c58ef8bbc5 | ||
|
6d08e69690 | ||
|
fb6e8bc25b | ||
|
5475f47bcf | ||
|
0986e822d2 | ||
|
e20e513039 | ||
|
8f19d56f00 | ||
|
6089b9078e | ||
|
5957735a0b | ||
|
1cbdd390f3 | ||
|
5311a2ef01 | ||
|
fa962e7bd7 | ||
|
97d571cb43 | ||
|
7b0082026c | ||
|
94a4585317 | ||
|
f4de21187d | ||
|
a036e4d2f9 | ||
|
a97705ffa9 | ||
|
8f45d76b5c | ||
|
f7562e1b0f | ||
|
58699190a4 | ||
|
aa1d7f87b9 | ||
|
5d1abd89bf | ||
|
42bea0fbc0 | ||
|
f57fd551e2 | ||
|
5b967d46a8 | ||
|
d3fea3ebb2 | ||
|
105d3b3c4e | ||
|
77d43fb7bd | ||
|
dbc47c7297 | ||
|
ef3ba02a89 | ||
|
a0709f2248 | ||
|
fecf77bcde | ||
|
ad3c394322 | ||
|
1279d597ac | ||
|
43d1084047 | ||
|
aa81f78587 | ||
|
4442cd030f | ||
|
4f6241a4bb | ||
|
f5ada9947b | ||
|
0222bb0829 | ||
|
a3d06b1271 | ||
|
99c17ff796 | ||
|
ffa3730f2f | ||
|
3a5965be08 | ||
|
418a8d9f67 | ||
|
d84ce6ddb9 | ||
|
bd42a7d06e | ||
|
dc42f2589d | ||
|
0323af09e5 | ||
|
ef01cabccc | ||
|
5a46575dc2 | ||
|
114be7a15d | ||
|
bfb0316583 | ||
|
005241d97b | ||
|
33678ea022 | ||
|
e20170c996 | ||
|
313a5bff9c | ||
|
1afff1e38f | ||
|
823c9ff1c5 | ||
|
31b3646fc8 | ||
|
8754a1b232 | ||
|
1aca9eb22c | ||
|
bea6ba6881 | ||
|
e4313067b6 | ||
|
decf9b7f6b | ||
|
5c53fd927b | ||
|
72e9b3e5b8 | ||
|
8777441d92 | ||
|
f964cf40a4 | ||
|
9865968f46 | ||
|
890e0e22e3 | ||
|
d6d22a82bc | ||
|
aa798fad85 | ||
|
2b7f5e7b70 | ||
|
677d6c923d | ||
|
62aa1b87d0 | ||
|
ecf9e24469 | ||
|
bfb5a5bb78 | ||
|
99942381cc | ||
|
fbadaca3e3 | ||
|
0be299fbd0 | ||
|
1c7d7af975 | ||
|
f93bade5df | ||
|
80232132c5 | ||
|
852114d639 | ||
|
cc06e9bbd8 | ||
|
0b7cee6cc9 | ||
|
fdda5768fe | ||
|
58e7732996 | ||
|
ebb8dcdc02 | ||
|
430a67adfa | ||
|
b95c079a28 | ||
|
a296cc3189 | ||
|
02c323e74a | ||
|
444e6184d8 | ||
|
f12ae4e7d0 | ||
|
cdb6559b32 | ||
|
ab7315e2f1 | ||
|
e633814061 | ||
|
68bcde5562 | ||
|
e3bcdb9c82 | ||
|
6820a8ab62 | ||
|
887ee1ceb2 | ||
|
69a12761c7 | ||
|
c4979cf750 | ||
|
28742d77e9 | ||
|
1ff128890d | ||
|
b2013b6f5e | ||
|
aaa0de4dbc | ||
|
a7c9804995 | ||
|
6cd52c123c | ||
|
dda8f64276 | ||
|
1ca1ef5c84 | ||
|
63f68328ff | ||
|
074159f442 | ||
|
45e3d8459e | ||
|
77407eb5b7 | ||
|
1d1db50a9f | ||
|
de23a0e3e1 | ||
|
d357e02544 | ||
|
e2731bc570 | ||
|
56be846035 | ||
|
79326cb64e | ||
|
b9110f9293 | ||
|
f72309b431 | ||
|
0808283543 | ||
|
01096d5db8 |
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.sh text eol=lf
|
||||||
|
gradlew text eol=lf
|
4
.gitignore
vendored
@ -22,3 +22,7 @@ target
|
|||||||
build
|
build
|
||||||
.gradle
|
.gradle
|
||||||
out
|
out
|
||||||
|
build.xml
|
||||||
|
proguard-project.txt
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
37
.project
@ -1,37 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<projectDescription>
|
|
||||||
<name>k9mail</name>
|
|
||||||
<comment></comment>
|
|
||||||
<projects>
|
|
||||||
<project>k9mail-ActionBarSherlock</project>
|
|
||||||
<project>k9mail-Android-PullToRefresh</project>
|
|
||||||
<project>k9mail-ckChangeLog</project>
|
|
||||||
<project>k9mail-HoloColorPicker</project>
|
|
||||||
</projects>
|
|
||||||
<buildSpec>
|
|
||||||
<buildCommand>
|
|
||||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
|
||||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
|
||||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
</buildSpec>
|
|
||||||
<natures>
|
|
||||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
|
||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
|
||||||
</natures>
|
|
||||||
</projectDescription>
|
|
8
.tx/config
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[main]
|
||||||
|
host = https://www.transifex.com
|
||||||
|
lang_map = af_ZA: af-rZA, am_ET: am-rET, ar_AE: ar-rAE, ar_BH: ar-rBH, ar_DZ: ar-rDZ, ar_EG: ar-rEG, ar_IQ: ar-rIQ, ar_JO: ar-rJO, ar_KW: ar-rKW, ar_LB: ar-rLB, ar_LY: ar-rLY, ar_MA: ar-rMA, ar_OM: ar-rOM, ar_QA: ar-rQA, ar_SA: ar-rSA, ar_SY: ar-rSY, ar_TN: ar-rTN, ar_YE: ar-rYE, arn_CL: arn-rCL, as_IN: as-rIN, az_AZ: az-rAZ, ba_RU: ba-rRU, be_BY: be-rBY, bg_BG: bg-rBG, bn_BD: bn-rBD, bn_IN: bn-rIN, bo_CN: bo-rCN, br_FR: br-rFR, bs_BA: bs-rBA, ca_ES: ca-rES, co_FR: co-rFR, cs_CZ: cs-rCZ, cy_GB: cy-rGB, da_DK: da-rDK, de_AT: de-rAT, de_CH: de-rCH, de_DE: de-rDE, de_LI: de-rLI, de_LU: de-rLU, dsb_DE: dsb-rDE, dv_MV: dv-rMV, el_GR: el-rGR, en_AU: en-rAU, en_BZ: en-rBZ, en_CA: en-rCA, en_GB: en-rGB, en_IE: en-rIE, en_IN: en-rIN, en_JM: en-rJM, en_MY: en-rMY, en_NZ: en-rNZ, en_PH: en-rPH, en_SG: en-rSG, en_TT: en-rTT, en_US: en-rUS, en_ZA: en-rZA, en_ZW: en-rZW, es_AR: es-rAR, es_BO: es-rBO, es_CL: es-rCL, es_CO: es-rCO, es_CR: es-rCR, es_DO: es-rDO, es_EC: es-rEC, es_ES: es-rES, es_GT: es-rGT, es_HN: es-rHN, es_MX: es-rMX, es_NI: es-rNI, es_PA: es-rPA, es_PE: es-rPE, es_PR: es-rPR, es_PY: es-rPY, es_SV: es-rSV, es_US: es-rUS, es_UY: es-rUY, es_VE: es-rVE, et_EE: et-rEE, eu_ES: eu-rES, fa_IR: fa-rIR, fi_FI: fi-rFI, fil_PH: fil-rPH, fo_FO: fo-rFO, fr_BE: fr-rBE, fr_CA: fr-rCA, fr_CH: fr-rCH, fr_FR: fr-rFR, fr_LU: fr-rLU, fr_MC: fr-rMC, fy_NL: fy-rNL, ga_IE: ga-rIE, gd_GB: gd-rGB, gl_ES: gl-rES, gsw_FR: gsw-rFR, gu_IN: gu-rIN, ha_NG: ha-rNG, hi_IN: hi-rIN, hr_BA: hr-rBA, hr_HR: hr-rHR, hsb_DE: hsb-rDE, hu_HU: hu-rHU, hy_AM: hy-rAM, id_ID: id-rID, ig_NG: ig-rNG, ii_CN: ii-rCN, is_IS: is-rIS, it_CH: it-rCH, it_IT: it-rIT, iu_CA: iu-rCA, ja_JP: ja-rJP, ka_GE: ka-rGE, kk_KZ: kk-rKZ, kl_GL: kl-rGL, km_KH: km-rKH, kn_IN: kn-rIN, ko_KR: ko-rKR, kok_IN: kok-rIN, ky_KG: ky-rKG, lb_LU: lb-rLU, lo_LA: lo-rLA, lt_LT: lt-rLT, lv_LV: lv-rLV, mi_NZ: mi-rNZ, mk_MK: mk-rMK, ml_IN: ml-rIN, mn_CN: mn-rCN, mn_MN: mn-rMN, moh_CA: moh-rCA, mr_IN: mr-rIN, ms_BN: ms-rBN, ms_MY: ms-rMY, mt_MT: mt-rMT, nb_NO: nb-rNO, ne_NP: ne-rNP, nl_BE: nl-rBE, nl_NL: nl-rNL, nn_NO: nn-rNO, nso_ZA: nso-rZA, oc_FR: oc-rFR, or_IN: or-rIN, pa_IN: pa-rIN, pl_PL: pl-rPL, prs_AF: prs-rAF, ps_AF: ps-rAF, pt_BR: pt-rBR, pt_PT: pt-rPT, qut_GT: qut-rGT, quz_BO: quz-rBO, quz_EC: quz-rEC, quz_PE: quz-rPE, rm_CH: rm-rCH, ro_RO: ro-rRO, ru_RU: ru-rRU, rw_RW: rw-rRW, sa_IN: sa-rIN, sah_RU: sah-rRU, se_FI: se-rFI, se_NO: se-rNO, se_SE: se-rSE, si_LK: si-rLK, sk_SK: sk-rSK, sl_SI: sl-rSI, sma_NO: sma-rNO, sma_SE: sma-rSE, smj_NO: smj-rNO, smj_SE: smj-rSE, smn_FI: smn-rFI, sms_FI: sms-rFI, sq_AL: sq-rAL, sr_BA: sr-rBA, sr_CS: sr-rCS, sr_ME: sr-rME, sr_RS: sr-rRS, sv_FI: sv-rFI, sv_SE: sv-rSE, sw_KE: sw-rKE, syr_SY: syr-rSY, ta_IN: ta-rIN, te_IN: te-rIN, tg_TJ: tg-rTJ, th_TH: th-rTH, tk_TM: tk-rTM, tn_ZA: tn-rZA, tr_TR: tr-rTR, tt_RU: tt-rRU, tzm_DZ: tzm-rDZ, ug_CN: ug-rCN, uk_UA: uk-rUA, ur_PK: ur-rPK, uz_UZ: uz-rUZ, vi_VN: vi-rVN, wo_SN: wo-rSN, xh_ZA: xh-rZA, yo_NG: yo-rNG, zh_CN: zh-rCN, zh_HK: zh-rHK, zh_MO: zh-rMO, zh_SG: zh-rSG, zh_TW: zh-rTW, zu_ZA: zu-rZA, no_NO: no-rNO, he_IL: iw-rIL, he: iw
|
||||||
|
|
||||||
|
[k9mail.strings]
|
||||||
|
file_filter = k9mail/src/main/res/values-<lang>/strings.xml
|
||||||
|
source_file = k9mail/src/main/res/values/strings.xml
|
||||||
|
source_lang = en
|
@ -1,63 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
|
||||||
<component name="FacetManager">
|
|
||||||
<facet type="android" name="Android">
|
|
||||||
<configuration>
|
|
||||||
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/plugins/ActionBarSherlock/library/target/generated-sources/r" />
|
|
||||||
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/plugins/ActionBarSherlock/library/target/generated-sources/aidl" />
|
|
||||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/plugins/ActionBarSherlock/library/AndroidManifest.xml" />
|
|
||||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/plugins/ActionBarSherlock/library/res" />
|
|
||||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/plugins/ActionBarSherlock/library/assets" />
|
|
||||||
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/plugins/ActionBarSherlock/library/ignored" />
|
|
||||||
<option name="USE_CUSTOM_APK_RESOURCE_FOLDER" value="false" />
|
|
||||||
<option name="CUSTOM_APK_RESOURCE_FOLDER" value="/plugins/ActionBarSherlock/library/target/generated-sources/combined-resources/res" />
|
|
||||||
<option name="USE_CUSTOM_COMPILER_MANIFEST" value="false" />
|
|
||||||
<option name="CUSTOM_COMPILER_MANIFEST" value="" />
|
|
||||||
<option name="APK_PATH" value="/plugins/ActionBarSherlock/library/target/library.apk" />
|
|
||||||
<option name="LIBRARY_PROJECT" value="true" />
|
|
||||||
<option name="RUN_PROCESS_RESOURCES_MAVEN_TASK" value="false" />
|
|
||||||
<option name="GENERATE_UNSIGNED_APK" value="false" />
|
|
||||||
<option name="CUSTOM_DEBUG_KEYSTORE_PATH" value="" />
|
|
||||||
<option name="PACK_TEST_CODE" value="false" />
|
|
||||||
<option name="RUN_PROGUARD" value="false" />
|
|
||||||
<option name="PROGUARD_CFG_PATH" value="/proguard-project.txt" />
|
|
||||||
<resOverlayFolders>
|
|
||||||
<path>/plugins/ActionBarSherlock/library/res-overlay</path>
|
|
||||||
</resOverlayFolders>
|
|
||||||
<includeSystemProguardFile>true</includeSystemProguardFile>
|
|
||||||
<includeAssetsFromLibraries>true</includeAssetsFromLibraries>
|
|
||||||
<additionalNativeLibs />
|
|
||||||
</configuration>
|
|
||||||
</facet>
|
|
||||||
</component>
|
|
||||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="false">
|
|
||||||
<output url="file://$MODULE_DIR$/plugins/ActionBarSherlock/library/target/classes" />
|
|
||||||
<output-test url="file://$MODULE_DIR$/plugins/ActionBarSherlock/library/target/test-classes" />
|
|
||||||
<content url="file://$MODULE_DIR$/plugins/ActionBarSherlock/library">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/plugins/ActionBarSherlock/library/target/generated-sources/r" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/plugins/ActionBarSherlock/library/gen" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/plugins/ActionBarSherlock/library/src" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/plugins/ActionBarSherlock/library/test" isTestSource="true" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/ActionBarSherlock/library/target/classes" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/ActionBarSherlock/library/target/generated-sources/combined-assets" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/ActionBarSherlock/library/target/generated-sources/combined-resources" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/ActionBarSherlock/library/target/generated-sources/extracted-dependencies" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/ActionBarSherlock/library/target/test-classes" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.android:android:4.0.1.2" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-logging:commons-logging:1.1.1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpclient:4.0.1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpcore:4.0.1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-codec:commons-codec:1.3" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.khronos:opengl-api:gl1.1-android-2.1_r1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: xerces:xmlParserAPIs:2.6.2" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: xpp3:xpp3:1.1.4c" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.json:json:20080701" level="project" />
|
|
||||||
<orderEntry type="library" name="Maven: com.google.android:support-v4:r99" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.10" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.1" level="project" />
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
|
||||||
<component name="FacetManager">
|
|
||||||
<facet type="android" name="Android">
|
|
||||||
<configuration>
|
|
||||||
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/plugins/Android-PullToRefresh/library/target/generated-sources/r" />
|
|
||||||
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/plugins/Android-PullToRefresh/library/target/generated-sources/aidl" />
|
|
||||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/plugins/Android-PullToRefresh/library/AndroidManifest.xml" />
|
|
||||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/plugins/Android-PullToRefresh/library/res" />
|
|
||||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/plugins/Android-PullToRefresh/library/assets" />
|
|
||||||
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/plugins/Android-PullToRefresh/library/libs" />
|
|
||||||
<option name="USE_CUSTOM_APK_RESOURCE_FOLDER" value="false" />
|
|
||||||
<option name="CUSTOM_APK_RESOURCE_FOLDER" value="/plugins/Android-PullToRefresh/library/target/generated-sources/combined-resources/res" />
|
|
||||||
<option name="USE_CUSTOM_COMPILER_MANIFEST" value="false" />
|
|
||||||
<option name="CUSTOM_COMPILER_MANIFEST" value="" />
|
|
||||||
<option name="APK_PATH" value="/plugins/Android-PullToRefresh/library/target/library.apk" />
|
|
||||||
<option name="LIBRARY_PROJECT" value="true" />
|
|
||||||
<option name="RUN_PROCESS_RESOURCES_MAVEN_TASK" value="false" />
|
|
||||||
<option name="GENERATE_UNSIGNED_APK" value="false" />
|
|
||||||
<option name="CUSTOM_DEBUG_KEYSTORE_PATH" value="" />
|
|
||||||
<option name="PACK_TEST_CODE" value="false" />
|
|
||||||
<option name="RUN_PROGUARD" value="false" />
|
|
||||||
<option name="PROGUARD_CFG_PATH" value="/proguard-project.txt" />
|
|
||||||
<resOverlayFolders>
|
|
||||||
<path>/plugins/Android-PullToRefresh/library/res-overlay</path>
|
|
||||||
</resOverlayFolders>
|
|
||||||
<includeSystemProguardFile>true</includeSystemProguardFile>
|
|
||||||
<includeAssetsFromLibraries>true</includeAssetsFromLibraries>
|
|
||||||
<additionalNativeLibs />
|
|
||||||
</configuration>
|
|
||||||
</facet>
|
|
||||||
</component>
|
|
||||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="false">
|
|
||||||
<output url="file://$MODULE_DIR$/plugins/Android-PullToRefresh/library/target/classes" />
|
|
||||||
<output-test url="file://$MODULE_DIR$/plugins/Android-PullToRefresh/library/target/test-classes" />
|
|
||||||
<content url="file://$MODULE_DIR$/plugins/Android-PullToRefresh/library">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/plugins/Android-PullToRefresh/library/target/generated-sources/r" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/plugins/Android-PullToRefresh/library/gen" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/plugins/Android-PullToRefresh/library/src" isTestSource="false" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/Android-PullToRefresh/library/target/classes" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/Android-PullToRefresh/library/target/generated-sources/combined-assets" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/Android-PullToRefresh/library/target/generated-sources/combined-resources" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/Android-PullToRefresh/library/target/generated-sources/extracted-dependencies" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/Android-PullToRefresh/library/target/test-classes" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.android:android:4.1.1.4" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-logging:commons-logging:1.1.1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpclient:4.0.1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpcore:4.0.1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-codec:commons-codec:1.3" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.khronos:opengl-api:gl1.1-android-2.1_r1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: xerces:xmlParserAPIs:2.6.2" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: xpp3:xpp3:1.1.4c" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.json:json:20080701" level="project" />
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
|
|
35
Android.mk
@ -1,35 +0,0 @@
|
|||||||
LOCAL_PATH:= $(call my-dir)
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
|
|
||||||
LOCAL_STATIC_JAVA_LIBRARIES += libcore
|
|
||||||
LOCAL_STATIC_JAVA_LIBRARIES += libdom
|
|
||||||
LOCAL_STATIC_JAVA_LIBRARIES += libio
|
|
||||||
LOCAL_STATIC_JAVA_LIBRARIES += libjutf
|
|
||||||
LOCAL_STATIC_JAVA_LIBRARIES += libjzlib
|
|
||||||
LOCAL_STATIC_JAVA_LIBRARIES += libhtmlcleaner
|
|
||||||
|
|
||||||
LOCAL_MODULE_TAGS := eng
|
|
||||||
|
|
||||||
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
|
||||||
|
|
||||||
LOCAL_SDK_VERSION := current
|
|
||||||
|
|
||||||
LOCAL_PACKAGE_NAME := Email
|
|
||||||
|
|
||||||
include $(BUILD_PACKAGE)
|
|
||||||
##################################################
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
|
|
||||||
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libcore:libs/apache-mime4j-core-0.7-SNAPSHOT.jar
|
|
||||||
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libdom:libs/apache-mime4j-dom-0.7-SNAPSHOT.jar
|
|
||||||
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libio:libs/commons-io-2.0.1.jar
|
|
||||||
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libjutf:libs/jutf7-1.0.1-SNAPSHOT.jar
|
|
||||||
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libjzlib:libs/jzlib-1.0.7.jar
|
|
||||||
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libhtmlcleaner:libs/htmlcleaner-2.2.jar
|
|
||||||
|
|
||||||
include $(BUILD_MULTI_PREBUILT)
|
|
||||||
|
|
||||||
|
|
||||||
# Use the folloing include to make our test apk.
|
|
||||||
include $(call all-makefiles-under,$(LOCAL_PATH))
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
|
||||||
<component name="FacetManager">
|
|
||||||
<facet type="android" name="Android">
|
|
||||||
<configuration>
|
|
||||||
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/plugins/HoloColorPicker/target/generated-sources/r" />
|
|
||||||
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/plugins/HoloColorPicker/target/generated-sources/aidl" />
|
|
||||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/plugins/HoloColorPicker/AndroidManifest.xml" />
|
|
||||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/plugins/HoloColorPicker/res" />
|
|
||||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/plugins/HoloColorPicker/assets" />
|
|
||||||
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/plugins/HoloColorPicker/ignored" />
|
|
||||||
<option name="USE_CUSTOM_APK_RESOURCE_FOLDER" value="false" />
|
|
||||||
<option name="CUSTOM_APK_RESOURCE_FOLDER" value="/plugins/HoloColorPicker/target/generated-sources/combined-resources/res" />
|
|
||||||
<option name="USE_CUSTOM_COMPILER_MANIFEST" value="false" />
|
|
||||||
<option name="CUSTOM_COMPILER_MANIFEST" value="" />
|
|
||||||
<option name="APK_PATH" value="/plugins/HoloColorPicker/target/colorpicker.apk" />
|
|
||||||
<option name="LIBRARY_PROJECT" value="true" />
|
|
||||||
<option name="RUN_PROCESS_RESOURCES_MAVEN_TASK" value="false" />
|
|
||||||
<option name="GENERATE_UNSIGNED_APK" value="false" />
|
|
||||||
<option name="CUSTOM_DEBUG_KEYSTORE_PATH" value="" />
|
|
||||||
<option name="PACK_TEST_CODE" value="false" />
|
|
||||||
<option name="RUN_PROGUARD" value="false" />
|
|
||||||
<option name="PROGUARD_CFG_PATH" value="/proguard-project.txt" />
|
|
||||||
<resOverlayFolders>
|
|
||||||
<path>/plugins/HoloColorPicker/res-overlay</path>
|
|
||||||
</resOverlayFolders>
|
|
||||||
<includeSystemProguardFile>true</includeSystemProguardFile>
|
|
||||||
<includeAssetsFromLibraries>true</includeAssetsFromLibraries>
|
|
||||||
<additionalNativeLibs />
|
|
||||||
</configuration>
|
|
||||||
</facet>
|
|
||||||
</component>
|
|
||||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="false">
|
|
||||||
<output url="file://$MODULE_DIR$/plugins/HoloColorPicker/target/classes" />
|
|
||||||
<output-test url="file://$MODULE_DIR$/plugins/HoloColorPicker/target/test-classes" />
|
|
||||||
<content url="file://$MODULE_DIR$/plugins/HoloColorPicker">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/plugins/HoloColorPicker/target/generated-sources/r" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/plugins/HoloColorPicker/gen" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/plugins/HoloColorPicker/src" isTestSource="false" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/HoloColorPicker/target/classes" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/HoloColorPicker/target/generated-sources/combined-assets" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/HoloColorPicker/target/generated-sources/combined-resources" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/HoloColorPicker/target/generated-sources/extracted-dependencies" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/HoloColorPicker/target/test-classes" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.android:android:4.1.1.4" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-logging:commons-logging:1.1.1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpclient:4.0.1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpcore:4.0.1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-codec:commons-codec:1.3" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.khronos:opengl-api:gl1.1-android-2.1_r1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: xerces:xmlParserAPIs:2.6.2" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: xpp3:xpp3:1.1.4c" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.json:json:20080701" level="project" />
|
|
||||||
<orderEntry type="library" name="Maven: com.google.android:support-v4:r99" level="project" />
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
|
|
83
README.md
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# K-9 Mail
|
||||||
|
[![Build Status](https://k9mail.ci.cloudbees.com/job/master/badge/icon)](https://k9mail.ci.cloudbees.com/job/master/)
|
||||||
|
[![Join the chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/k9mail/k-9)
|
||||||
|
|
||||||
|
K-9 Mail is an open-source email client for Android.
|
||||||
|
|
||||||
|
|
||||||
|
## Download
|
||||||
|
|
||||||
|
K-9 Mail can be downloaded from a couple of sources:
|
||||||
|
|
||||||
|
- [Google Play](https://play.google.com/store/apps/details?id=com.fsck.k9)
|
||||||
|
- [F-Droid](https://f-droid.org/repository/browse/?fdid=com.fsck.k9)
|
||||||
|
- [Github Releases](https://github.com/k9mail/k-9/releases)
|
||||||
|
- [Amazon Appstore for Android](http://www.amazon.com/dp/B004JK61K0)
|
||||||
|
|
||||||
|
You might also be interested in becoming a [beta tester](https://github.com/k9mail/k-9/wiki/BetaTester)
|
||||||
|
or an [alpha tester](https://github.com/k9mail/k-9/wiki/AlphaTester) to get an early look at new versions.
|
||||||
|
|
||||||
|
|
||||||
|
## Release Notes
|
||||||
|
|
||||||
|
Check out the [Release Notes](https://github.com/k9mail/k-9/wiki/ReleaseNotes) to find out what changed
|
||||||
|
in each version of K-9 Mail.
|
||||||
|
|
||||||
|
|
||||||
|
## Need Help?
|
||||||
|
|
||||||
|
If the app is not behaving like it should, you might find these resources helpful:
|
||||||
|
|
||||||
|
- [User Manual](https://github.com/k9mail/k-9/wiki/Manual)
|
||||||
|
- [Frequently Asked Questions](https://github.com/k9mail/k-9/wiki/FrequentlyAskedQuestions)
|
||||||
|
- [Support Forum/Mailing List](http://groups.google.com/group/k-9-mail)
|
||||||
|
- [Google+ Community](https://plus.google.com/communities/109228641058741937099)
|
||||||
|
|
||||||
|
|
||||||
|
## Translations
|
||||||
|
|
||||||
|
Interested in helping to translate K-9 Mail? Contribute here:
|
||||||
|
|
||||||
|
https://www.transifex.com/projects/p/k9mail/
|
||||||
|
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please fork this repository and contribute back using [pull requests](https://github.com/k9mail/k-9/pulls).
|
||||||
|
|
||||||
|
Any contributions, large or small, major features, bug fixes, unit/integration tests are welcomed and appreciated
|
||||||
|
but will be thoroughly reviewed and discussed.
|
||||||
|
Please make sure you read the [Code Style Guidelines](https://github.com/k9mail/k-9/wiki/CodeStyle).
|
||||||
|
|
||||||
|
|
||||||
|
## Communication
|
||||||
|
|
||||||
|
Aside from discussing changes in [pull requests](https://github.com/k9mail/k-9/pulls) and
|
||||||
|
[issues](https://github.com/k9mail/k-9/issues) we use the following communication services:
|
||||||
|
|
||||||
|
- IRC chat, [#k-9 on the Freenode network](http://webchat.freenode.net/?channels=%23k-9)
|
||||||
|
- [Gitter](https://gitter.im/k9mail/k-9)
|
||||||
|
- [Developer mailing list](https://groups.google.com/forum/#!forum/k-9-dev)
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
CloudBees' [FOSS program](https://www.cloudbees.com/resources/foss) allows us to use their DEV@cloud service for free.
|
||||||
|
|
||||||
|
![built on DEV@cloud](https://www.cloudbees.com/sites/default/files/styles/large/public/Button-Built-on-CB-1.png)
|
||||||
|
|
@ -1,23 +0,0 @@
|
|||||||
# This file is used to override default values used by the Ant build system.
|
|
||||||
#
|
|
||||||
# This file must be checked in Version Control Systems, as it is
|
|
||||||
# integral to the build system of your project.
|
|
||||||
|
|
||||||
# This file is only used by the Ant script.
|
|
||||||
|
|
||||||
# You can use this to override default values such as
|
|
||||||
# 'source.dir' for the location of your java source folder and
|
|
||||||
# 'out.dir' for the location of your output folder.
|
|
||||||
|
|
||||||
# You can also use it define how the release builds are signed by declaring
|
|
||||||
# the following properties:
|
|
||||||
# 'key.store' for the location of your keystore and
|
|
||||||
# 'key.alias' for the name of the key to use.
|
|
||||||
# The password will be asked during the build when you use the 'release' target.
|
|
||||||
|
|
||||||
# Indicates whether an apk should be generated for each density.
|
|
||||||
split.density=false
|
|
||||||
java.encoding=utf8
|
|
||||||
# Project target.
|
|
||||||
target=android-15
|
|
||||||
extensible.libs.classpath=compile-only-libs
|
|
BIN
assets/icon.png
Before Width: | Height: | Size: 7.4 KiB |
49
build.gradle
@ -1,37 +1,22 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
jcenter()
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:0.5.+'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: 'android'
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile project(':plugins:ActionBarSherlock:library')
|
|
||||||
compile project(':plugins:Android-PullToRefresh:library')
|
|
||||||
compile project(':plugins:ckChangeLog:library')
|
|
||||||
compile project(':plugins:HoloColorPicker')
|
|
||||||
compile fileTree(dir: 'libs', include: '*.jar')
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdkVersion 17
|
|
||||||
buildToolsVersion '17'
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
main {
|
|
||||||
manifest.srcFile 'AndroidManifest.xml'
|
|
||||||
java.srcDirs = ['src']
|
|
||||||
res.srcDirs = ['res']
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instrumentTest {
|
dependencies {
|
||||||
manifest.srcFile 'tests/AndroidManifest.xml'
|
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||||
java.srcDirs = ['tests/src']
|
classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project.ext.preDexLibs = !project.hasProperty('disablePreDex')
|
||||||
|
project.ext.testCoverage = project.hasProperty('testCoverage')
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
project.plugins.whenPluginAdded { plugin ->
|
||||||
|
if ("com.android.build.gradle.AppPlugin".equals(plugin.class.name) ||
|
||||||
|
"com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)) {
|
||||||
|
project.android.dexOptions.preDexLibraries = rootProject.ext.preDexLibs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
461
build.xml
@ -1,461 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project name="K9" default="help">
|
|
||||||
|
|
||||||
<!-- The local.properties file is created and updated by the 'android' tool.
|
|
||||||
It contains the path to the SDK. It should *NOT* be checked into
|
|
||||||
Version Control Systems. -->
|
|
||||||
<property file="local.properties" />
|
|
||||||
|
|
||||||
<!-- The ant.properties file can be created by you. It is only edited by the
|
|
||||||
'android' tool to add properties to it.
|
|
||||||
This is the place to change some Ant specific build properties.
|
|
||||||
Here are some properties you may want to change/update:
|
|
||||||
|
|
||||||
source.dir
|
|
||||||
The name of the source directory. Default is 'src'.
|
|
||||||
out.dir
|
|
||||||
The name of the output directory. Default is 'bin'.
|
|
||||||
|
|
||||||
For other overridable properties, look at the beginning of the rules
|
|
||||||
files in the SDK, at tools/ant/build.xml
|
|
||||||
|
|
||||||
Properties related to the SDK location or the project target should
|
|
||||||
be updated using the 'android' tool with the 'update' action.
|
|
||||||
|
|
||||||
This file is an integral part of the build system for your
|
|
||||||
application and should be checked into Version Control Systems.
|
|
||||||
|
|
||||||
-->
|
|
||||||
<property file="ant.properties" />
|
|
||||||
|
|
||||||
<!-- The project.properties file is created and updated by the 'android'
|
|
||||||
tool, as well as ADT.
|
|
||||||
|
|
||||||
This contains project specific properties such as project target, and library
|
|
||||||
dependencies. Lower level build properties are stored in ant.properties
|
|
||||||
(or in .classpath for Eclipse projects).
|
|
||||||
|
|
||||||
This file is an integral part of the build system for your
|
|
||||||
application and should be checked into Version Control Systems. -->
|
|
||||||
<loadproperties srcFile="project.properties" />
|
|
||||||
|
|
||||||
<!-- quick check on sdk.dir -->
|
|
||||||
<fail
|
|
||||||
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var"
|
|
||||||
unless="sdk.dir"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Import per project custom build rules if present at the root of the project.
|
|
||||||
This is the place to put custom intermediary targets such as:
|
|
||||||
-pre-build
|
|
||||||
-pre-compile
|
|
||||||
-post-compile (This is typically used for code obfuscation.
|
|
||||||
Compiled code location: ${out.classes.absolute.dir}
|
|
||||||
If this is not done in place, override ${out.dex.input.absolute.dir})
|
|
||||||
-post-package
|
|
||||||
-post-build
|
|
||||||
-pre-clean
|
|
||||||
-->
|
|
||||||
<import file="custom_rules.xml" optional="true" />
|
|
||||||
|
|
||||||
<!-- Import the actual build file.
|
|
||||||
|
|
||||||
To customize existing targets, there are two options:
|
|
||||||
- Customize only one target:
|
|
||||||
- copy/paste the target into this file, *before* the
|
|
||||||
<import> task.
|
|
||||||
- customize it to your needs.
|
|
||||||
- Customize the whole content of build.xml
|
|
||||||
- copy/paste the content of the rules files (minus the top node)
|
|
||||||
into this file, replacing the <import> task.
|
|
||||||
- customize to your needs.
|
|
||||||
|
|
||||||
***********************
|
|
||||||
****** IMPORTANT ******
|
|
||||||
***********************
|
|
||||||
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
|
|
||||||
in order to avoid having your file be overridden by tools such as "android update project"
|
|
||||||
-->
|
|
||||||
<!-- version-tag: custom -->
|
|
||||||
<import file="${sdk.dir}/tools/ant/build.xml" />
|
|
||||||
|
|
||||||
<!-- K9 CUSTOM STUFF -->
|
|
||||||
|
|
||||||
<!-- out folders for a parent project if this project is an instrumentation project -->
|
|
||||||
<property name="rclib" value="${out.dir}/K9RemoteControl.jar" />
|
|
||||||
<property name="rcdir" value="com/fsck/k9/remotecontrol/**" />
|
|
||||||
|
|
||||||
<property name="changelog-path-src" value="res/xml/changelog_master.xml" />
|
|
||||||
|
|
||||||
<!-- Name given to the remote git repository -->
|
|
||||||
<property name="origin" value="origin" />
|
|
||||||
|
|
||||||
<!-- Name used for the temporary gh-pages branch in the local git repo -->
|
|
||||||
<property name="gh-pages-tmp" value="gh-pages-tmp" />
|
|
||||||
|
|
||||||
<condition property="android.executable" value="android.bat">
|
|
||||||
<os family="windows" />
|
|
||||||
</condition>
|
|
||||||
<property name="android.executable" value="android" />
|
|
||||||
|
|
||||||
<target name="-get-version" depends="-get-version-name">
|
|
||||||
<echo>Building version number ${current-version-name}</echo>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="-get-version-name">
|
|
||||||
<xpath input="AndroidManifest.xml" expression="/manifest/@android:versionName" output="current-version-name" />
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="-get-version-code">
|
|
||||||
<xpath input="AndroidManifest.xml" expression="/manifest/@android:versionCode" output="current-version-code" />
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="-get-version-from-git">
|
|
||||||
<exec executable="git" failonerror="true" outputproperty="current-version-name" errorproperty="current-version-error">
|
|
||||||
<arg line="describe --tags" />
|
|
||||||
</exec>
|
|
||||||
<echo>Building version number ${current-version-name}</echo>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="-auto-incr-version">
|
|
||||||
<regex property="major" input="${current-version-name}" regexp="(\d+)\.\d+" select="\1" />
|
|
||||||
<regex property="minor" input="${current-version-name}" regexp="\d+\.(\d+)" select="\1" />
|
|
||||||
<math result="minor" operand1="${minor}" operation="+" operand2="1" datatype="int"/>
|
|
||||||
<if.contrib>
|
|
||||||
<length string="${minor}" when="eq" length="1" />
|
|
||||||
<then>
|
|
||||||
<var name="minor" value="00${minor}" />
|
|
||||||
</then>
|
|
||||||
<elseif>
|
|
||||||
<length string="${minor}" when="eq" length="2" />
|
|
||||||
<then>
|
|
||||||
<var name="minor" value="0${minor}" />
|
|
||||||
</then>
|
|
||||||
</elseif>
|
|
||||||
</if.contrib>
|
|
||||||
<regex property="version-name" input="${major}." regexp="(\d+.)" replace="\1${minor}" />
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="-pre-bump-check" depends="-get-version-name,-auto-incr-version">
|
|
||||||
<xpath
|
|
||||||
input="${changelog-path-src}"
|
|
||||||
expression="/changelog/release[@version='${version-name}']/@version"
|
|
||||||
output="changelog-test" />
|
|
||||||
|
|
||||||
<if.contrib>
|
|
||||||
<equals arg1="${changelog-test}" arg2="${version-name}" />
|
|
||||||
<else>
|
|
||||||
<fail>No changelog for ${version-name}.</fail>
|
|
||||||
</else>
|
|
||||||
</if.contrib>
|
|
||||||
|
|
||||||
<exec executable="git" failonerror="true" outputproperty="git-status" errorproperty="git-status-error">
|
|
||||||
<arg line="status -s ${changelog-path-src}" />
|
|
||||||
</exec>
|
|
||||||
<if.contrib>
|
|
||||||
<equals arg1="${git-status}" arg2="" />
|
|
||||||
<else>
|
|
||||||
<fail>Uncomitted changelog edits.</fail>
|
|
||||||
</else>
|
|
||||||
</if.contrib>
|
|
||||||
|
|
||||||
<!-- Check for a clean index, because it will be reset in -update-gh-pages-branch -->
|
|
||||||
<exec executable="git" failonerror="true">
|
|
||||||
<arg line="diff-index --cached --quiet HEAD" />
|
|
||||||
</exec>
|
|
||||||
|
|
||||||
<!-- Check that the temporary gh-pages branch doesn't exist in the local git repo -->
|
|
||||||
<exec executable="git" failonerror="true" outputproperty="gh-pages-tmp-status" errorproperty="gh-pages-tmp-status-error">
|
|
||||||
<arg line="branch --list ${gh-pages-tmp}" />
|
|
||||||
</exec>
|
|
||||||
<if.contrib>
|
|
||||||
<equals arg1="${gh-pages-tmp-status}" arg2="" />
|
|
||||||
<else>
|
|
||||||
<fail>Temporary branch ${gh-pages-tmp} exists (but should not).</fail>
|
|
||||||
</else>
|
|
||||||
</if.contrib>
|
|
||||||
|
|
||||||
<!-- Assure that we have the latest gh-pages branch -->
|
|
||||||
<exec executable="git" failonerror="true">
|
|
||||||
<arg line="fetch ${origin} +refs/heads/gh-pages:refs/remotes/${origin}/gh-pages" />
|
|
||||||
</exec>
|
|
||||||
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="-set-version" depends="-get-version-name,-get-version-code">
|
|
||||||
<!-- pass -Dversion-name=4.200 to define the version instead of auto-incrementing it -->
|
|
||||||
<if.contrib>
|
|
||||||
<isset property="version-name" />
|
|
||||||
<else>
|
|
||||||
<runtarget target="-auto-incr-version" />
|
|
||||||
</else>
|
|
||||||
</if.contrib>
|
|
||||||
|
|
||||||
<echo>Setting version to ${version-name}</echo>
|
|
||||||
|
|
||||||
<replace file="AndroidManifest.xml"
|
|
||||||
token="android:versionName="${current-version-name}""
|
|
||||||
value="android:versionName="${version-name}"" summary="true"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<math result="new-version-code" operand1="${current-version-code}" operation="+" operand2="1" datatype="int"/>
|
|
||||||
<replace file="AndroidManifest.xml"
|
|
||||||
token="android:versionCode="${current-version-code}""
|
|
||||||
value="android:versionCode="${new-version-code}"" summary="true"
|
|
||||||
/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<!-- rules -->
|
|
||||||
<target name="bump-version" depends="-pre-bump-check,-set-version,-commit-version,-update-gh-pages-branch,-push-version">
|
|
||||||
<echo>Bumped K-9 to ${version-name}</echo>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="-commit-version">
|
|
||||||
<exec executable="git" failonerror="true">
|
|
||||||
<arg line="commit -m'Bumped manifest to ${version-name}' AndroidManifest.xml" />
|
|
||||||
</exec>
|
|
||||||
<exec executable="git" failonerror="true">
|
|
||||||
<arg line="tag ${version-name}" />
|
|
||||||
</exec>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<!-- Copy the changelog to the gh-pages branch. -->
|
|
||||||
<target name="-update-gh-pages-branch">
|
|
||||||
|
|
||||||
<!-- Create a temporary branch for use in updating the remote gh-pages branch. -->
|
|
||||||
<exec executable="git" failonerror="true">
|
|
||||||
<arg line="branch ${gh-pages-tmp} ${origin}/gh-pages" />
|
|
||||||
</exec>
|
|
||||||
|
|
||||||
<!-- Save HEAD before switching branches -->
|
|
||||||
<exec executable="git" failonerror="true" outputproperty="git-branch-ref" errorproperty="git-branch-ref-error">
|
|
||||||
<arg line="symbolic-ref HEAD" />
|
|
||||||
</exec>
|
|
||||||
|
|
||||||
<!-- Switch to the temporary branch with no checkout. The working tree remains untouched. -->
|
|
||||||
<exec executable="git" failonerror="true">
|
|
||||||
<arg line="symbolic-ref HEAD refs/heads/${gh-pages-tmp}" />
|
|
||||||
</exec>
|
|
||||||
|
|
||||||
<!-- Clean up the index on the temporary branch -->
|
|
||||||
<exec executable="git" failonerror="true">
|
|
||||||
<arg line="reset -q" />
|
|
||||||
</exec>
|
|
||||||
|
|
||||||
<!-- Retrieve tree info for the changelog file to be copied from HEAD -->
|
|
||||||
<exec executable="git" failonerror="true" outputproperty="git-ls-tree" errorproperty="git-ls-tree-error">
|
|
||||||
<arg line="ls-tree ${git-branch-ref} ${changelog-path-src}" />
|
|
||||||
</exec>
|
|
||||||
|
|
||||||
<!-- Update the path and name of the changelog for where it will be stored in the temp. branch -->
|
|
||||||
<regex property="changelog-path-dst" input="${git-branch-ref}" regexp=".*/([^/]+$)" select="changelog_\1_branch.xml" />
|
|
||||||
<regex property="git-index-info" input="${git-ls-tree}" regexp="(.*\t).*" select="\1${changelog-path-dst}" />
|
|
||||||
|
|
||||||
<!-- Add the changelog to the index -->
|
|
||||||
<exec executable="git" failonerror="true" inputstring="${git-index-info}">
|
|
||||||
<arg line="update-index --index-info" />
|
|
||||||
</exec>
|
|
||||||
|
|
||||||
<!-- Commit the changelog -->
|
|
||||||
<exec executable="git" failonerror="true">
|
|
||||||
<arg line="commit -m'Update changelog for version ${version-name}'" />
|
|
||||||
</exec>
|
|
||||||
|
|
||||||
<!-- Switch back to HEAD, again without touching the (original) working tree -->
|
|
||||||
<exec executable="git" failonerror="true">
|
|
||||||
<arg line="symbolic-ref HEAD ${git-branch-ref}" />
|
|
||||||
</exec>
|
|
||||||
|
|
||||||
<!-- Clean up the index for HEAD -->
|
|
||||||
<exec executable="git" failonerror="true">
|
|
||||||
<arg line="reset -q" />
|
|
||||||
</exec>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="-push-version">
|
|
||||||
<exec executable="git" failonerror="true">
|
|
||||||
<arg line="push ${origin} HEAD ${gh-pages-tmp}:gh-pages tag ${version-name}" />
|
|
||||||
</exec>
|
|
||||||
|
|
||||||
<!-- Delete the temporary branch -->
|
|
||||||
<exec executable="git" failonerror="true">
|
|
||||||
<arg line="branch -D ${gh-pages-tmp}" />
|
|
||||||
</exec>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<!-- Create the output directories if they don't exist yet. -->
|
|
||||||
<target name="rclib" depends="-compile">
|
|
||||||
<echo>Creating library ${rclib} for remote control applications</echo>
|
|
||||||
<jar destfile="${rclib}" basedir="${out.classes.dir}" includes="${rcdir}" />
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="upload" depends="clean,-get-version,release">
|
|
||||||
<echo>Uploading to Google Code using Google::Code::Upload</echo>
|
|
||||||
<move file="${out.final.file}" tofile="bin/k9-${current-version-name}-release.apk" />
|
|
||||||
<property name="gcode-project" value="k9mail" />
|
|
||||||
<exec executable="googlecode_upload.pl" failonerror="true">
|
|
||||||
<arg value="--summary" />
|
|
||||||
<arg value="${ant.project.name} ${current-version-name}" />
|
|
||||||
<arg value="--project" />
|
|
||||||
<arg value="${gcode-project}" />
|
|
||||||
<arg value="--user" />
|
|
||||||
<arg value="${gcode-user}" />
|
|
||||||
<arg value="--pass" />
|
|
||||||
<arg value="${gcode-pass}" />
|
|
||||||
<arg value="--labels" />
|
|
||||||
<arg value="Type-Installer" />
|
|
||||||
<arg value="bin/k9-${current-version-name}-release.apk" />
|
|
||||||
</exec>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="astyle">
|
|
||||||
<exec executable="astyle" failonerror="true">
|
|
||||||
<arg line="--style=java --indent=spaces=4 --indent-switches --max-instatement-indent=4 --brackets=attach --add-brackets --convert-tabs --unpad-paren --pad-header --pad-oper --suffix=none --recursive 'src/com/fsck/k9/*.java' 'tests/src/com/fsck/k9/*.java'" />
|
|
||||||
</exec>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="help" depends="android_rules.help">
|
|
||||||
<!-- displays starts at col 13
|
|
||||||
|13 80| -->
|
|
||||||
<echo>Additional targets:</echo>
|
|
||||||
<!--echo> bump-version: ant -Dversion-name=3.123</echo>
|
|
||||||
<echo> Bumps the project version to 3.123,tags and commits it.</echo>
|
|
||||||
<echo> If version-name is not given, it will auto-increment.</echo>
|
|
||||||
<echo> upload: Uploads a new release to google code.</echo-->
|
|
||||||
<echo> rclib: Creates library for remote control applications.</echo>
|
|
||||||
<echo> astyle: Make K-9's source look like it's supposed to.</echo>
|
|
||||||
<echo> eclipse: Apply template Eclipse settings.</echo>
|
|
||||||
<echo> javadoc: Javadoc output to javadoc/. ANDROID_HOME environment</echo>
|
|
||||||
<echo> variable must be set (i.e. /opt/android-sdk-linux/).</echo>
|
|
||||||
<echo> lint-xml: Lint output lint-results.xml.</echo>
|
|
||||||
<echo> lint-html: Lint output to lint-results.html.</echo>
|
|
||||||
<echo> monkey: Runs monkey on the running emulator. Change the</echo>
|
|
||||||
<echo> defaults -Dmonkey.seed=NUM and -Dmonkey.count=NUM</echo>
|
|
||||||
<echo> from 0 and 200, respectively.</echo>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="eclipse" description="Apply template Eclipse settings">
|
|
||||||
<copy todir=".settings">
|
|
||||||
<fileset dir="tools/eclipse-settings" />
|
|
||||||
</copy>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="monkey">
|
|
||||||
<xpath input="AndroidManifest.xml" expression="/manifest/@package" output="manifest.package" />
|
|
||||||
<property name="monkey.count" value="200" />
|
|
||||||
<property name="monkey.seed" value="0" /><!-- largest == 9223372036854775807 == 2**63 - 1 -->
|
|
||||||
<exec executable="${adb}" output="monkey.txt" failonerror="true">
|
|
||||||
<arg line="${adb.device.arg}" />
|
|
||||||
<arg value="-e" />
|
|
||||||
<arg value="shell" />
|
|
||||||
<arg value="monkey" />
|
|
||||||
<arg value="-p" />
|
|
||||||
<arg value="${manifest.package}" />
|
|
||||||
<arg value="-v" />
|
|
||||||
<arg value="-v" />
|
|
||||||
<arg value="-s" />
|
|
||||||
<arg value="${monkey.seed}" />
|
|
||||||
<arg value="${monkey.count}" />
|
|
||||||
</exec>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="reg" depends="-get-version-code">
|
|
||||||
<regex property="branch" input="${env.GIT_BRANCH}" regexp="(?:.*/)?(.+)" select="\1" global="true"/>
|
|
||||||
<regex property="commit" input="${env.GIT_COMMIT}" regexp="([\da-fA-F]{10})" select="\1" global="true"/>
|
|
||||||
<math result="version-code" operand1="${current-version-code}" operation="+" operand2="1" datatype="int"/>
|
|
||||||
<echo message="branch = ${branch} ${commit} ${current-version-code} ${version-code}" />
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<!-- this is for CloudBees. see tests/build.xml -->
|
|
||||||
<target name="-artifactd" depends="-set-debug-files, -artifact" />
|
|
||||||
<target name="-artifacti" depends="-set-instrumented-mode, -artifact" />
|
|
||||||
<target name="-artifact">
|
|
||||||
<regex property="branch" input="${env.GIT_BRANCH}" regexp="(?:.*/)?(.+)" select="\1" global="true" />
|
|
||||||
<regex property="commit" input="${env.GIT_COMMIT}" regexp="([\da-fA-F]{10})" select="\1" />
|
|
||||||
|
|
||||||
<copy file="${out.final.file}"
|
|
||||||
tofile="${out.dir}/${ant.project.name}-${branch}-${env.BUILD_ID}-${commit}-${env.BUILD_NUMBER}.apk"
|
|
||||||
verbose="on"
|
|
||||||
/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="-pre-clean" description="Removes testing output and javadoc">
|
|
||||||
<delete file="monkey.txt" verbose="${verbose}" />
|
|
||||||
<delete file="lint-results.xml" verbose="${verbose}" />
|
|
||||||
<delete file="lint-results.html" verbose="${verbose}" />
|
|
||||||
<delete dir="lint-results_files" verbose="${verbose}" />
|
|
||||||
<delete dir="${javadoc-dir}" verbose="${verbose}" />
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="-update-abs">
|
|
||||||
<if.contrib>
|
|
||||||
<resourceexists>
|
|
||||||
<file file="plugins/ActionBarSherlock/library/build.xml" />
|
|
||||||
</resourceexists>
|
|
||||||
<else>
|
|
||||||
<echo message="android update lib-project -p plugins/ActionBarSherlock/library/" />
|
|
||||||
<exec executable="${sdk.dir}/tools/${android.executable}" failonerror="true">
|
|
||||||
<arg line="update lib-project -p plugins/ActionBarSherlock/library/" />
|
|
||||||
</exec>
|
|
||||||
</else>
|
|
||||||
</if.contrib>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="-update-ptr">
|
|
||||||
<if.contrib>
|
|
||||||
<resourceexists>
|
|
||||||
<file file="plugins/Android-PullToRefresh/library/build.xml" />
|
|
||||||
</resourceexists>
|
|
||||||
<else>
|
|
||||||
<echo message="android update lib-project -p plugins/Android-PullToRefresh/library/" />
|
|
||||||
<exec executable="${sdk.dir}/tools/${android.executable}" failonerror="true">
|
|
||||||
<arg line="update lib-project -p plugins/Android-PullToRefresh/library/" />
|
|
||||||
</exec>
|
|
||||||
</else>
|
|
||||||
</if.contrib>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="-update-cl">
|
|
||||||
<if.contrib>
|
|
||||||
<resourceexists>
|
|
||||||
<file file="plugins/ckChangeLog/library/build.xml" />
|
|
||||||
</resourceexists>
|
|
||||||
<else>
|
|
||||||
<echo message="android update lib-project -p plugins/ckChangeLog/library/" />
|
|
||||||
<exec executable="${sdk.dir}/tools/${android.executable}" failonerror="true">
|
|
||||||
<arg line="update lib-project -p plugins/ckChangeLog/library/" />
|
|
||||||
</exec>
|
|
||||||
</else>
|
|
||||||
</if.contrib>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="-update-hcp">
|
|
||||||
<if.contrib>
|
|
||||||
<resourceexists>
|
|
||||||
<file file="plugins/HoloColorPicker/build.xml" />
|
|
||||||
</resourceexists>
|
|
||||||
<else>
|
|
||||||
<echo message="android update lib-project -p plugins/HoloColorPicker/" />
|
|
||||||
<exec executable="${sdk.dir}/tools/${android.executable}" failonerror="true">
|
|
||||||
<arg line="update lib-project -p plugins/HoloColorPicker/" />
|
|
||||||
</exec>
|
|
||||||
</else>
|
|
||||||
</if.contrib>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="init" depends="-update-abs, -update-ptr, -update-cl, -update-hcp"
|
|
||||||
description="Initialize environment for building" />
|
|
||||||
|
|
||||||
<!-- overrides default "debug" target" -->
|
|
||||||
<!-- Builds debug output package -->
|
|
||||||
<target name="debug" depends="init, -set-debug-files, -do-debug, -post-build"
|
|
||||||
description="Builds the application and signs it with a debug key.">
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<!-- common to both build.xml and tests/build.xml -->
|
|
||||||
<import file="build_common.xml" />
|
|
||||||
|
|
||||||
<!-- END K-9 CUSTOM STUFF -->
|
|
||||||
|
|
||||||
</project>
|
|
@ -1,96 +0,0 @@
|
|||||||
<project name="common">
|
|
||||||
<!-- This file contains scriptdefs, properties, targets, etc that are common
|
|
||||||
to both build.xml and tests/build.xml. It also loads ant-contrib, where
|
|
||||||
each desired task needs to be defined below as both ant-contrib and
|
|
||||||
Android's anttasks.jar define different 'if' tasks. -->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- ANT-CONTRIB -->
|
|
||||||
|
|
||||||
<!-- jar file from where the tasks are loaded -->
|
|
||||||
<if>
|
|
||||||
<condition>
|
|
||||||
<isset property="tested.project.dir" />
|
|
||||||
</condition>
|
|
||||||
<then>
|
|
||||||
<path id="antcontrib">
|
|
||||||
<pathelement path="${tested.project.dir}/tools/ant-contrib.jar" />
|
|
||||||
</path>
|
|
||||||
</then>
|
|
||||||
<else>
|
|
||||||
<path id="antcontrib">
|
|
||||||
<pathelement path="tools/ant-contrib.jar" />
|
|
||||||
</path>
|
|
||||||
</else>
|
|
||||||
</if>
|
|
||||||
|
|
||||||
<!-- ant-contrib tasks -->
|
|
||||||
<!-- this is normally named propertyregex -->
|
|
||||||
<taskdef name="regex"
|
|
||||||
classname="net.sf.antcontrib.property.RegexTask"
|
|
||||||
classpathref="antcontrib" />
|
|
||||||
<taskdef name="math"
|
|
||||||
classname="net.sf.antcontrib.math.MathTask"
|
|
||||||
classpathref="antcontrib" />
|
|
||||||
<taskdef name="runtarget"
|
|
||||||
classname="net.sf.antcontrib.logic.RunTargetTask"
|
|
||||||
classpathref="antcontrib" />
|
|
||||||
<taskdef name="var"
|
|
||||||
classname="net.sf.antcontrib.property.Variable"
|
|
||||||
classpathref="antcontrib" />
|
|
||||||
<!-- renamed to not conflict with android -->
|
|
||||||
<taskdef name="if.contrib"
|
|
||||||
classname="net.sf.antcontrib.logic.IfTask"
|
|
||||||
classpathref="antcontrib" />
|
|
||||||
|
|
||||||
|
|
||||||
<!-- SCRIPTDEFS -->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- PROPERTIES -->
|
|
||||||
|
|
||||||
<!-- allow environment variables to be accessable by prepending "env." -->
|
|
||||||
<property environment="env" />
|
|
||||||
|
|
||||||
<!-- javadoc folder relative to ${basedir} -->
|
|
||||||
<property name="javadoc-dir" location="javadoc" />
|
|
||||||
|
|
||||||
<!-- path to lint -->
|
|
||||||
<property name="lint" location="${android.tools.dir}/lint${bat}" />
|
|
||||||
|
|
||||||
|
|
||||||
<!-- TARGETS -->
|
|
||||||
|
|
||||||
<!-- create javadoc in ${javadoc-dir} -->
|
|
||||||
<target name="javadoc" description="build javadoc">
|
|
||||||
<mkdir dir="${javadoc-dir}"/>
|
|
||||||
<javadoc
|
|
||||||
destdir="${javadoc-dir}"
|
|
||||||
doctitle="K-9 Mail"
|
|
||||||
verbose="on"
|
|
||||||
use="true"
|
|
||||||
classpath="${env.ANDROID_HOME}/platforms/${target}/android.jar"
|
|
||||||
sourcepath="gen;src"
|
|
||||||
linkoffline="http://d.android.com/reference ${env.ANDROID_HOME}/docs/reference/"
|
|
||||||
/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<!-- create lint-results.xml -->
|
|
||||||
<target name="lint-xml">
|
|
||||||
<exec executable="${lint}" failonerror="true">
|
|
||||||
<arg value="--xml" />
|
|
||||||
<arg value="lint-results.xml" />
|
|
||||||
<arg path="${basedir}" />
|
|
||||||
</exec>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<!-- create lint-results.html and lint-results_files/ -->
|
|
||||||
<target name="lint-html">
|
|
||||||
<exec executable="${lint}" failonerror="true">
|
|
||||||
<arg value="--html" />
|
|
||||||
<arg value="lint-results.html" />
|
|
||||||
<arg path="${basedir}" />
|
|
||||||
</exec>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
</project>
|
|
@ -1,59 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
|
||||||
<component name="FacetManager">
|
|
||||||
<facet type="android" name="Android">
|
|
||||||
<configuration>
|
|
||||||
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/plugins/ckChangeLog/library/target/generated-sources/r" />
|
|
||||||
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/plugins/ckChangeLog/library/target/generated-sources/aidl" />
|
|
||||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/plugins/ckChangeLog/library/AndroidManifest.xml" />
|
|
||||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/plugins/ckChangeLog/library/res" />
|
|
||||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/plugins/ckChangeLog/library/assets" />
|
|
||||||
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/plugins/ckChangeLog/library/ignored" />
|
|
||||||
<option name="USE_CUSTOM_APK_RESOURCE_FOLDER" value="false" />
|
|
||||||
<option name="CUSTOM_APK_RESOURCE_FOLDER" value="/plugins/ckChangeLog/library/target/generated-sources/combined-resources/res" />
|
|
||||||
<option name="USE_CUSTOM_COMPILER_MANIFEST" value="false" />
|
|
||||||
<option name="CUSTOM_COMPILER_MANIFEST" value="" />
|
|
||||||
<option name="APK_PATH" value="/plugins/ckChangeLog/library/target/library.apk" />
|
|
||||||
<option name="LIBRARY_PROJECT" value="true" />
|
|
||||||
<option name="RUN_PROCESS_RESOURCES_MAVEN_TASK" value="false" />
|
|
||||||
<option name="GENERATE_UNSIGNED_APK" value="false" />
|
|
||||||
<option name="CUSTOM_DEBUG_KEYSTORE_PATH" value="" />
|
|
||||||
<option name="PACK_TEST_CODE" value="false" />
|
|
||||||
<option name="RUN_PROGUARD" value="false" />
|
|
||||||
<option name="PROGUARD_CFG_PATH" value="/proguard-project.txt" />
|
|
||||||
<resOverlayFolders>
|
|
||||||
<path>/plugins/ckChangeLog/library/res-overlay</path>
|
|
||||||
</resOverlayFolders>
|
|
||||||
<includeSystemProguardFile>true</includeSystemProguardFile>
|
|
||||||
<includeAssetsFromLibraries>true</includeAssetsFromLibraries>
|
|
||||||
<additionalNativeLibs />
|
|
||||||
</configuration>
|
|
||||||
</facet>
|
|
||||||
</component>
|
|
||||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="false">
|
|
||||||
<output url="file://$MODULE_DIR$/plugins/ckChangeLog/library/target/classes" />
|
|
||||||
<output-test url="file://$MODULE_DIR$/plugins/ckChangeLog/library/target/test-classes" />
|
|
||||||
<content url="file://$MODULE_DIR$/plugins/ckChangeLog/library">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/plugins/ckChangeLog/library/target/generated-sources/r" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/plugins/ckChangeLog/library/gen" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/plugins/ckChangeLog/library/src" isTestSource="false" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/ckChangeLog/library/target/classes" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/ckChangeLog/library/target/generated-sources/combined-assets" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/ckChangeLog/library/target/generated-sources/combined-resources" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/ckChangeLog/library/target/generated-sources/extracted-dependencies" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/plugins/ckChangeLog/library/target/maven-archiver" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.android:android:4.1.1.4" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-logging:commons-logging:1.1.1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpclient:4.0.1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpcore:4.0.1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-codec:commons-codec:1.3" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.khronos:opengl-api:gl1.1-android-2.1_r1" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: xerces:xmlParserAPIs:2.6.2" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: xpp3:xpp3:1.1.4c" level="project" />
|
|
||||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.json:json:20080701" level="project" />
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
|
|
149
config/checkstyle/checkstyle.xml
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE module PUBLIC
|
||||||
|
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||||
|
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
|
||||||
|
|
||||||
|
<module name="Checker">
|
||||||
|
<!--
|
||||||
|
If you set the basedir property below, then all reported file
|
||||||
|
names will be relative to the specified directory. See
|
||||||
|
http://checkstyle.sourceforge.net/5.x/config.html#Checker
|
||||||
|
|
||||||
|
<property name="basedir" value="${basedir}"/>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<property name="severity" value="info"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<module name="SuppressionFilter">
|
||||||
|
<property name="file" value="${checkstyle.suppressions.file}"/>
|
||||||
|
</module>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<module name="FileTabCharacter">
|
||||||
|
<property name="eachLine" value="false"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="NewlineAtEndOfFile"/>
|
||||||
|
|
||||||
|
<module name="TreeWalker">
|
||||||
|
<property name="tabWidth" value="4"/>
|
||||||
|
|
||||||
|
<module name="AvoidStarImport"/>
|
||||||
|
<module name="ConstantName">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
<module name="EmptyBlock">
|
||||||
|
<property name="option" value="text"/>
|
||||||
|
<property name="tokens" value="LITERAL_CATCH"/>
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
<module name="EmptyForIteratorPad"/>
|
||||||
|
<module name="EqualsHashCode">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
<module name="OneStatementPerLine"/>
|
||||||
|
|
||||||
|
<!-- module name="IllegalCatch"/ -->
|
||||||
|
<module name="IllegalImport">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
<module name="IllegalThrows">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
<module name="InnerAssignment">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="LeftCurly">
|
||||||
|
<property name="option" value="eol"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="LineLength">
|
||||||
|
<property name="max" value="140"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="LocalFinalVariableName">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
<module name="LocalVariableName">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
<module name="MemberName">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
<module name="MethodName">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
<module name="MethodParamPad"/>
|
||||||
|
<module name="ModifierOrder"/>
|
||||||
|
<module name="NeedBraces"/>
|
||||||
|
<module name="NoWhitespaceAfter">
|
||||||
|
<property name="tokens" value="BNOT"/>
|
||||||
|
<property name="tokens" value="DEC"/>
|
||||||
|
<property name="tokens" value="DOT"/>
|
||||||
|
<property name="tokens" value="INC"/>
|
||||||
|
<property name="tokens" value="LNOT"/>
|
||||||
|
<property name="tokens" value="UNARY_MINUS"/>
|
||||||
|
<property name="tokens" value="UNARY_PLUS"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="NoWhitespaceBefore"/>
|
||||||
|
<module name="NoWhitespaceBefore">
|
||||||
|
<property name="tokens" value="DOT"/>
|
||||||
|
<property name="allowLineBreaks" value="true"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="PackageName">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
<module name="ParameterName">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
<module name="ParenPad"/>
|
||||||
|
<module name="TypecastParenPad"/>
|
||||||
|
<module name="RedundantImport"/>
|
||||||
|
<module name="RedundantModifier"/>
|
||||||
|
<module name="RightCurly">
|
||||||
|
<property name="option" value="alone"/>
|
||||||
|
<property name="tokens" value="LITERAL_ELSE"/>
|
||||||
|
</module>
|
||||||
|
<module name="SimplifyBooleanExpression"/>
|
||||||
|
<module name="SimplifyBooleanReturn"/>
|
||||||
|
<module name="TypeName">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
<module name="UnusedImports"/>
|
||||||
|
<module name="UpperEll"/>
|
||||||
|
<module name="WhitespaceAfter"/>
|
||||||
|
<module name="WhitespaceAround"/>
|
||||||
|
<module name="GenericWhitespace"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<module name="MissingSwitchDefault"/>
|
||||||
|
<module name="MagicNumber"/>
|
||||||
|
<module name="Indentation"/>
|
||||||
|
|
||||||
|
<module name="OperatorWrap">
|
||||||
|
<property name="option" value="eol"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="EqualsAvoidNull">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<module name="ParameterAssignment">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="DefaultComesLast"/>
|
||||||
|
<module name="MissingDeprecated"/>
|
||||||
|
<module name="MissingOverride">
|
||||||
|
<property name="javaFiveCompatibility" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="OuterTypeFilename">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
</module>
|
||||||
|
</module>
|
9
config/findbugs/exclude_filter.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<FindBugsFilter>
|
||||||
|
<Match>
|
||||||
|
<Class name="~com\.fsck\.k9\.R.*" />
|
||||||
|
</Match>
|
||||||
|
<Match>
|
||||||
|
<Class name="~android\..*" />
|
||||||
|
</Match>
|
||||||
|
</FindBugsFilter>
|
9
config/findbugs/include_filter.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<FindBugsFilter>
|
||||||
|
<Match>
|
||||||
|
<Package name="~com\.fsck\.k9.*" />
|
||||||
|
<Not>
|
||||||
|
<Bug pattern="VA_FORMAT_STRING_USES_NEWLINE" />
|
||||||
|
</Not>
|
||||||
|
</Match>
|
||||||
|
</FindBugsFilter>
|
18
config/lint/lint.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<lint>
|
||||||
|
<issue id="MissingTranslation" severity="ignore" />
|
||||||
|
<issue id="OldTargetApi" severity="ignore" />
|
||||||
|
|
||||||
|
<!-- Transifex and Lint disagree on what quantities are necessary -->
|
||||||
|
<issue id="UnusedQuantity" severity="warning">
|
||||||
|
<ignore path="src/main/res/values-*/strings.xml" />
|
||||||
|
</issue>
|
||||||
|
<issue id="MissingQuantity" severity="warning">
|
||||||
|
<ignore path="src/main/res/values-*/strings.xml" />
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<!-- Remove this when we have separate Transifex resources for plugins -->
|
||||||
|
<issue id="ExtraTranslation" severity="error">
|
||||||
|
<ignore path="src/main/res/values-zh-rTW/plugin_strings.xml" />
|
||||||
|
</issue>
|
||||||
|
</lint>
|
23
docs/TESTS
@ -1,23 +0,0 @@
|
|||||||
Some simple functional tests
|
|
||||||
--
|
|
||||||
|
|
||||||
Compose a message
|
|
||||||
Attach an image to the message
|
|
||||||
Save the message as a draft
|
|
||||||
Reopen the draft
|
|
||||||
* Is the attachment still there?
|
|
||||||
Send the message.
|
|
||||||
* Does the received message have the correct attachment?
|
|
||||||
|
|
||||||
Check delete functionality on POP and IMAP account.
|
|
||||||
|
|
||||||
Check delete functionality on IMAP with no network connection.
|
|
||||||
|
|
||||||
Check save draft functionality on POP and IMAP account.
|
|
||||||
|
|
||||||
Check save draft functionality on IMAP with no network connection.
|
|
||||||
|
|
||||||
Check sent message functionality on POP and IMAP account.
|
|
||||||
|
|
||||||
Check sent functionality on IMAP with no network connection.
|
|
||||||
* When the network is brought back does the sent message get uploaded?
|
|
27
docs/TODO
@ -1,27 +0,0 @@
|
|||||||
Currently
|
|
||||||
--
|
|
||||||
Need to add NOOP checking to Pop3Store and ImapStore on cached connections.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
In the future
|
|
||||||
--
|
|
||||||
Move attachments to files, instead of storing as blobs in the database. There are tons of ways
|
|
||||||
we can make the app perform better with this small change. Primarily, we can do everything
|
|
||||||
pertaining to large attachments as streams instead of as large loads into byte arrays.
|
|
||||||
|
|
||||||
Get rid of the LocalStore's attachment to Store altogether. Local storage is too complex and
|
|
||||||
specific to performance to be bound to the Store API. It needs to be flexible with plenty of helper
|
|
||||||
functions to make best use of memory and resources.
|
|
||||||
|
|
||||||
Make better use of the abstractions for Body, Part and BodyPart. Proper use of these abstractions
|
|
||||||
can completely remove the need for the special headers.
|
|
12
gradle/plugins/checkstyle-android.gradle
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apply plugin: 'checkstyle'
|
||||||
|
|
||||||
|
check.dependsOn 'checkstyle'
|
||||||
|
task checkstyle(type: Checkstyle) {
|
||||||
|
ignoreFailures = true
|
||||||
|
configFile file("$rootProject.projectDir/config/checkstyle/checkstyle.xml")
|
||||||
|
|
||||||
|
source = project.android.sourceSets.main.java.getSrcDirs() +
|
||||||
|
project.android.sourceSets.androidTest.java.getSrcDirs()
|
||||||
|
include '**/*.java'
|
||||||
|
classpath = files()
|
||||||
|
}
|
29
gradle/plugins/findbugs-android.gradle
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
apply plugin: 'findbugs'
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
def variants = plugins.hasPlugin('com.android.application') ?
|
||||||
|
android.applicationVariants : android.libraryVariants
|
||||||
|
|
||||||
|
variants.each { variant ->
|
||||||
|
def task = project.task("findBugs${variant.name.capitalize()}", type: FindBugs) {
|
||||||
|
group = 'verification'
|
||||||
|
description = "Run FindBugs for the ${variant.description}."
|
||||||
|
|
||||||
|
effort = 'max'
|
||||||
|
ignoreFailures = true
|
||||||
|
|
||||||
|
includeFilter = file("$rootProject.projectDir/config/findbugs/include_filter.xml")
|
||||||
|
excludeFilter = file("$rootProject.projectDir/config/findbugs/exclude_filter.xml")
|
||||||
|
|
||||||
|
def variantCompile = variant.javaCompile
|
||||||
|
|
||||||
|
classes = fileTree(variantCompile.destinationDir)
|
||||||
|
source = variantCompile.source
|
||||||
|
classpath = variantCompile.classpath.plus(project.files(android.bootClasspath))
|
||||||
|
|
||||||
|
dependsOn(variantCompile)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.getByName('check').dependsOn(task)
|
||||||
|
}
|
||||||
|
}
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#Sun Dec 07 14:12:42 GMT 2014
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
|
164
gradlew
vendored
Executable file
@ -0,0 +1,164 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn ( ) {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die ( ) {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||||
|
if $cygwin ; then
|
||||||
|
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >&-
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >&-
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||||
|
function splitJvmOpts() {
|
||||||
|
JVM_OPTS=("$@")
|
||||||
|
}
|
||||||
|
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||||
|
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
gradlew.bat
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windowz variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
goto execute
|
||||||
|
|
||||||
|
:4NT_args
|
||||||
|
@rem Get arguments from the 4NT Shell from JP Software
|
||||||
|
set CMD_LINE_ARGS=%$
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
11
images/drawable-src/ic_action_delete.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||||
|
<path fill="#FFFFFF" d="M17.5,10.156L17,19.313c0,0-2.281,1.469-5,1.469S7,19.25,7,19.25l-0.5-9.094c0,0,2.969,0.938,5.5,0.938
|
||||||
|
S17.5,10.156,17.5,10.156z"/>
|
||||||
|
<path fill="#FFFFFF" d="M14.479,6.17V3.844H9.521V6.17C7.428,6.469,5.969,7.133,5.969,7.906c0,1.053,2.7,1.906,6.031,1.906
|
||||||
|
s6.031-0.854,6.031-1.906C18.031,7.133,16.572,6.469,14.479,6.17z M10.306,6.078V4.656h3.375v1.42C13.146,6.027,12.584,6,12,6
|
||||||
|
C11.411,6,10.844,6.028,10.306,6.078z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 909 B |
10
images/drawable-src/ic_action_mark_as_read.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||||
|
<polygon fill="#FFFFFF" points="3,11 3,7.612 12,3.338 21,7.612 21,11 19,11 19,9 4,9 4,11 "/>
|
||||||
|
<circle fill="#FFFFFF" cx="12" cy="14.612" r="1.788"/>
|
||||||
|
<path fill="#FFFFFF" d="M14.646,14.966c-0.175,1.313-1.287,2.329-2.646,2.329c-1.36,0-2.472-1.017-2.646-2.329L3,11.612V21h18
|
||||||
|
v-9.388L14.646,14.966z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 769 B |
10
images/drawable-src/ic_action_single_message_options.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||||
|
<path fill="#FFFFFF" d="M17.938,10C15.168,10,14,10,14,10v4c0,0,1.843,0,2.939,0c1.18,0,2.078,1.338,2.5,3.18l3.49-1.888
|
||||||
|
C22.93,15.292,21.133,10,17.938,10z"/>
|
||||||
|
<polygon fill="#FFFFFF" points="14,4.791 6.825,12.005 14,19.208 "/>
|
||||||
|
<polygon fill="#FFFFFF" points="1.825,12.005 9,19.208 9,15.09 5.938,12 9,8.91 9,4.791 "/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 786 B |
12
images/drawables-pgp/status_lock_closed.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||||
|
<!-- Generator: Sketch 3.0.4 (8053) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>lock-closed</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||||
|
<g id="lock-closed" sketch:type="MSArtboardGroup" fill="#000000">
|
||||||
|
<path d="M81.502,45.132 L79.577,45.132 L79.577,29.479 C79.479,10.285 66.387,-0.164 50.476,-0.164 C34.57,-0.164 20.304,10.782 20.801,29.479 L20.785,45.112 C20.785,45.112 21.025,45.133 19.825,45.133 C18.555,45.133 10.185,46.606 10.185,54.069 L10.185,89.893 C10.185,97.852 19.605,99.836 19.825,99.836 L81.027,99.836 C81.247,99.836 90.181,98.843 90.181,89.893 L90.181,54.564 C90.182,46.109 81.727,45.132 81.502,45.132 L81.502,45.132 Z M59.334,86.055 L41.061,86.055 L46.024,71.489 C43.904,70.077 42.496,67.623 42.496,64.824 C42.496,60.44 45.938,56.886 50.183,56.886 C54.428,56.886 57.87,60.443 57.87,64.824 C57.87,67.619 56.466,70.077 54.348,71.485 L59.334,86.055 L59.334,86.055 Z M34.261,45.132 L34.277,29.686 C34.277,19.737 40.348,11.783 50.183,11.783 C59.924,11.783 66.088,18.741 66.088,29.686 L66.098,45.132 L34.261,45.132 L34.261,45.132 Z" sketch:type="MSShapeGroup"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
12
images/drawables-pgp/status_lock_error.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||||
|
<!-- Generator: Sketch 3.0.4 (8053) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>lock-error</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||||
|
<g id="lock-error" sketch:type="MSArtboardGroup" fill="#000000">
|
||||||
|
<path d="M80.459,45.474 L78.533,45.474 L78.533,29.826 C78.435,10.633 65.344,0.183 49.433,0.183 C33.527,0.183 19.265,11.128 19.761,29.826 L19.745,45.454 C19.745,45.454 19.985,45.475 18.784,45.475 C17.514,45.475 9.145,46.946 9.145,54.407 L9.145,90.228 C9.145,98.187 18.565,100.171 18.784,100.171 L79.984,100.171 C80.203,100.171 89.138,99.178 89.138,90.228 L89.138,54.901 C89.139,46.452 80.684,45.474 80.459,45.474 L80.459,45.474 Z M33.234,30.033 C33.234,20.084 39.304,12.131 49.14,12.131 C58.881,12.131 65.045,19.088 65.045,30.033 L65.055,45.474 L33.218,45.474 L33.234,30.033 L33.234,30.033 Z M59.4033767,90.873 L48.4582822,79.9279055 L38.2296593,90.3644491 L31.6365,83.7568884 L42.5824946,72.8153942 L32.3439707,62.5939721 L38.7544118,56.1079235 L49.7013065,67.0503177 L60.1234487,56.7100837 L66.6365,63.2240351 L55.6896053,74.1673294 L66.0091373,84.4778605 L59.4033767,90.873 Z" sketch:type="MSShapeGroup"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
12
images/drawables-pgp/status_lock_open.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||||
|
<!-- Generator: Sketch 3.0.4 (8053) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>lock-open</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||||
|
<g id="lock-open" sketch:type="MSArtboardGroup" fill="#000000">
|
||||||
|
<path d="M79.577,26.479 C79.577,10.833 66.387,-0.164 50.476,-0.164 C34.57,-0.164 20.304,10.782 20.801,29.479 L20.785,45.112 C20.785,45.112 21.025,45.133 19.825,45.133 C18.555,45.133 10.185,46.606 10.185,54.069 L10.185,89.893 C10.185,97.852 19.605,99.836 19.825,99.836 L81.027,99.836 C81.247,99.836 90.181,98.843 90.181,89.893 L90.181,54.564 C90.181,46.107 81.726,45.13 81.5,45.13 L34.259,45.13 L34.275,29.684 C34.275,19.735 40.346,11.781 50.181,11.781 C59.922,11.781 66.664,18.164 66.664,29.164 L79.577,26.479 Z M59.334,86.055 L41.061,86.055 L46.024,71.49 C43.904,70.078 42.496,67.624 42.496,64.825 C42.496,60.44 45.938,56.887 50.183,56.887 C54.428,56.887 57.87,60.445 57.87,64.825 C57.87,67.62 56.466,70.078 54.348,71.485 L59.334,86.055 L59.334,86.055 Z" sketch:type="MSShapeGroup"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
12
images/drawables-pgp/status_signature_expired_cutout.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||||
|
<!-- Generator: Sketch 3.0.4 (8053) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>signature-expired-cutout</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||||
|
<g id="signature-expired-cutout" sketch:type="MSArtboardGroup" transform="translate(0.110156, 0.000000)" fill="#000000">
|
||||||
|
<path d="M5.21763502,25.9334098 C4.62201801,24.5421709 5.31408066,20.2649627 6.50270803,18.5737297 C7.69394204,16.8824967 14.8139764,11.1118682 18.0827017,9.51888655 C21.3514269,7.92852492 25.1232335,10.7136228 25.1232335,10.7136228 L31.1693326,18.5737297 C21.6564037,21.754453 12.2477403,33.889148 12.2477403,33.889148 C12.2477403,33.889148 5.81325202,27.3259588 5.21763502,25.9334098 Z M50.7969868,98.0040129 C30.5564763,98.0040129 14.1592664,81.3860653 14.1592664,60.910451 C14.1592664,41.4138456 29.0387981,25.4459175 47.9423376,23.9707712 L47.9423376,18.3341735 L41.6333009,18.3341735 L41.6333009,9.02828206 L59.2561767,9.02828206 L59.2561767,18.3341735 L53.6477076,18.3341735 L53.6477076,23.9707712 C72.5460092,25.4445909 87.4333977,41.412519 87.4333977,60.910451 C87.4333977,81.3847387 71.0296405,98.0040129 50.7969868,98.0040129 Z M51.541054,71.6933659 C57.6539179,71.6933659 62.6093732,66.7455263 62.6093732,60.6420567 C62.6093732,54.5385872 57.6539179,49.5907476 51.541054,49.5907476 C45.4281901,49.5907476 40.4727348,54.5385872 40.4727348,60.6420567 C40.4727348,66.7455263 45.4281901,71.6933659 51.541054,71.6933659 Z M96.3766201,25.9341425 C95.7811759,27.3252533 89.3433427,33.889148 89.3433427,33.889148 C89.3433427,33.889148 79.9321974,21.7542607 70.4233315,18.5751403 L76.4676764,10.7157573 C76.4676764,10.7157573 80.2396916,7.92829614 83.5087714,9.5185113 C86.7765483,11.1152759 93.8997286,16.884063 95.0880111,18.5751403 C96.2775965,20.2635977 96.9694584,24.540412 96.3766201,25.9341425 Z" sketch:type="MSShapeGroup"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
12
images/drawables-pgp/status_signature_invalid_cutout.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||||
|
<!-- Generator: Sketch 3.0.4 (8053) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>signature-invalid-cutout</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||||
|
<g id="signature-invalid-cutout" sketch:type="MSArtboardGroup" transform="translate(0.110156, 0.000000)" fill="#000000">
|
||||||
|
<path d="M77.3119658,92 L50,64.6787909 L22.6865385,92 L8.00299145,77.3054987 L35.3149573,49.9977557 L8,22.6870202 L22.6850427,8.00149623 L50,35.3137279 L77.3149573,8 L92,22.6825315 L64.6850427,49.9977557 L91.9970085,77.3054987 L77.3119658,92 Z" sketch:type="MSShapeGroup"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 995 B |
12
images/drawables-pgp/status_signature_revoked_cutout.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="101px" height="100px" viewBox="0 0 101 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||||
|
<!-- Generator: Sketch 3.0.4 (8053) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>signature-revoked-cutout</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||||
|
<g id="signature-revoked-cutout" sketch:type="MSArtboardGroup" transform="translate(0.915625, 0.000000)" fill="#000000">
|
||||||
|
<path d="M50.1457786,95.2902674 C25.2543974,95.2902674 5,75.0407401 5,50.1451337 C5,25.252107 25.2556872,5 50.1457786,5 C75.03587,5 95.2902674,25.252107 95.2902674,50.1451337 C95.2902674,75.0394503 75.0371599,95.2902674 50.1457786,95.2902674 Z M35.5297191,75.6701923 C39.8404345,78.1467253 44.8296167,79.569442 50.1464236,79.569442 C66.3793238,79.569442 79.5862102,66.3638454 79.5862102,50.1296554 C79.5862102,44.8115586 78.1622037,39.8223764 75.6843808,35.5116611 L35.5297191,75.6701923 Z M50.1464236,20.6911586 C33.9135233,20.6911586 20.7066369,33.8967551 20.7066369,50.1309452 C20.7066369,55.3523024 22.0803389,60.2563538 24.473031,64.512895 L64.5296632,24.4575526 C60.2718321,22.0635707 55.3690706,20.6911586 50.1464236,20.6911586 Z" sketch:type="MSShapeGroup"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
12
images/drawables-pgp/status_signature_unknown_cutout.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="101px" height="100px" viewBox="0 0 101 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||||
|
<!-- Generator: Sketch 3.0.4 (8053) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>signature-unknown-cutout</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||||
|
<g id="signature-unknown-cutout" sketch:type="MSArtboardGroup" transform="translate(0.915625, 0.000000)" fill="#000000">
|
||||||
|
<path d="M11.4743662,97.2253545 C1.98936285,97.2253571 -1.69987039,86.6466353 1.98936288,81.2764443 C2.36018089,80.2888073 37.5445854,9.4248374 37.6406733,9.21698534 C41.524789,0.483122973 56.8650161,0.0416071437 60.7924391,9.21698534 C60.7572519,9.19524917 98.2991929,81.8687547 97.9337883,81.2642177 C101.323931,86.2404407 96.9260512,97.2253571 88.8978453,97.2253545 C88.8978453,97.2253545 11.4756386,97.2879401 11.4743662,97.2253545 Z M50.5378687,73.3388569 C47.2443918,73.3388569 44.2703808,76.046195 44.2703808,79.5061732 C44.2703808,82.9729198 47.1388056,85.6802579 50.5378687,85.6802579 C53.9369317,85.6802579 56.8040029,82.9729198 56.8040029,79.5061732 C56.8053565,76.046195 53.8313455,73.3388569 50.5378687,73.3388569 Z M50.3063913,28.5 C46.5729719,28.5 42.719076,30.2990258 43.0805057,32.9143334 L45.8826007,65.934287 L54.7315355,65.934287 L57.5322768,32.9143334 C57.8937065,30.2990258 54.0398106,28.5 50.3063913,28.5 Z" sketch:type="MSShapeGroup"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
12
images/drawables-pgp/status_signature_unverified_cutout.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="101px" height="100px" viewBox="0 0 101 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||||
|
<!-- Generator: Sketch 3.0.4 (8053) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>signature-unverified-cutout</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||||
|
<g id="signature-unverified-cutout" sketch:type="MSArtboardGroup" transform="translate(0.915625, 0.000000)" fill="#000000">
|
||||||
|
<path d="M49.8900274,96.5521596 C75.8474106,96.5521596 96.8900274,75.5095428 96.8900274,49.5521596 C96.8900274,23.5947764 75.8474106,2.5521596 49.8900274,2.5521596 C23.9326441,2.5521596 2.89002736,23.5947764 2.89002736,49.5521596 C2.89002736,75.5095428 23.9326441,96.5521596 49.8900274,96.5521596 Z M42.9188472,79.4349375 L42.9188472,67.0146143 L55.3391704,67.0146143 L55.3391704,79.4349375 L42.9188472,79.4349375 Z M68.652586,41.8646591 C67.9712562,43.583078 67.1302842,45.0524085 66.1249189,46.2716815 C65.1167028,47.4919237 64.0039592,48.5318919 62.7800362,49.3906167 C61.5570634,50.25128 60.4006082,51.1080664 59.3135213,51.9677605 C58.2254842,52.829393 57.2609796,53.8121774 56.4181072,54.9209598 C55.5733342,56.0307115 55.0449948,57.4147511 54.8273874,59.0779247 L54.8273874,62.237567 L43.8168316,62.237567 L43.8168316,58.4954263 C43.9802747,56.114064 44.4278428,54.1165111 45.1623867,52.5095519 C45.89503,50.9025928 46.7531065,49.5292145 47.7309145,48.3923248 C48.7106231,47.2564044 49.7425954,46.2716815 50.8296823,45.4400947 C51.9177195,44.6104463 52.9230848,43.7769211 53.8476788,42.9463035 C54.7722728,42.1147166 55.5201202,41.2007465 56.0912209,40.2024546 C56.6623216,39.2041628 56.9188893,37.9577517 56.8647251,36.4612832 C56.8647251,33.9112774 56.2537138,32.0261534 55.030741,30.804942 C53.8058678,29.5876075 52.1058691,28.9760325 49.9326456,28.9760325 C48.4645081,28.9760325 47.2006746,29.2677664 46.1392445,29.8492956 C45.0797149,30.4317941 44.2092851,31.2071664 43.5298558,32.177351 C42.848526,33.1475357 42.3477439,34.2844253 42.0208576,35.5860816 C41.6939713,36.8906456 41.5305282,38.2901926 41.5305282,39.7866612 L29.5431146,39.7866612 C29.5953784,36.7917856 30.0999615,34.0479368 31.0521128,31.5541455 C32.0023636,29.0593851 33.3346152,26.8970655 35.0479174,25.0691252 C36.7612195,23.2382773 38.8270647,21.8115922 41.2464032,20.7832547 C43.6666919,19.7568556 46.3730062,19.2441406 49.3624951,19.2441406 C53.2224138,19.2441406 56.4447142,19.785932 59.0274958,20.8666072 C61.6093272,21.9482516 63.6894262,23.2935226 65.2668425,24.9024202 C66.8442588,26.5103485 67.9712562,28.2423365 68.6516357,30.0993532 C69.3310651,31.9573391 69.6703046,33.6883579 69.6703046,35.2962862 C69.6722051,37.9567825 69.3320153,40.1481786 68.652586,41.8646591 Z" sketch:type="MSShapeGroup"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
12
images/drawables-pgp/status_signature_verified_cutout.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||||
|
<!-- Generator: Sketch 3.0.4 (8053) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>signature-verified-cutout</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||||
|
<g id="signature-verified-cutout" sketch:type="MSArtboardGroup" transform="translate(0.110156, 0.000000)" fill="#000000">
|
||||||
|
<path d="M50,97 C75.9573832,97 97,75.9573832 97,50 C97,24.0426168 75.9573832,3 50,3 C24.0426168,3 3,24.0426168 3,50 C3,75.9573832 24.0426168,97 50,97 Z M46.2732912,77.5085 L20,57.830916 L27.9184401,47.6349702 L43.3096859,59.5152262 L70.31112,23 L80.867825,30.7782191 L46.2732912,77.5085 Z" sketch:type="MSShapeGroup"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
images/feature_graphic.png
Normal file
After Width: | Height: | Size: 73 KiB |
1479
images/feature_graphic.svg
Normal file
After Width: | Height: | Size: 84 KiB |
19
images/update-drawables-pgp.sh
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
APP_DIR=../k9mail/src/main
|
||||||
|
MDPI_DIR=$APP_DIR/res/drawable-mdpi
|
||||||
|
HDPI_DIR=$APP_DIR/res/drawable-hdpi
|
||||||
|
XDPI_DIR=$APP_DIR/res/drawable-xhdpi
|
||||||
|
XXDPI_DIR=$APP_DIR/res/drawable-xxhdpi
|
||||||
|
XXXDPI_DIR=$APP_DIR/res/drawable-xxxhdpi
|
||||||
|
SRC_DIR=./drawables-pgp/
|
||||||
|
|
||||||
|
|
||||||
|
for NAME in "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout"
|
||||||
|
do
|
||||||
|
echo $NAME
|
||||||
|
inkscape -w 24 -h 24 -e "$MDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
|
||||||
|
inkscape -w 32 -h 32 -e "$HDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
|
||||||
|
inkscape -w 48 -h 48 -e "$XDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
|
||||||
|
inkscape -w 64 -h 64 -e "$XXDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
|
||||||
|
done
|
65
k9mail-library/build.gradle
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply from: '../gradle/plugins/checkstyle-android.gradle'
|
||||||
|
apply from: '../gradle/plugins/findbugs-android.gradle'
|
||||||
|
apply plugin: 'jacoco'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile 'org.apache.james:apache-mime4j-core:0.7.2'
|
||||||
|
compile 'org.apache.james:apache-mime4j-dom:0.7.2'
|
||||||
|
compile 'commons-io:commons-io:2.4'
|
||||||
|
compile 'com.jcraft:jzlib:1.0.7'
|
||||||
|
compile 'com.beetstra.jutf7:jutf7:1.0.0'
|
||||||
|
|
||||||
|
androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
|
||||||
|
androidTestCompile 'com.madgag.spongycastle:pg:1.51.0.0'
|
||||||
|
|
||||||
|
testCompile('org.robolectric:robolectric:3.0-rc3') {
|
||||||
|
exclude group: 'org.hamcrest', module: 'hamcrest-core'
|
||||||
|
}
|
||||||
|
testCompile 'org.hamcrest:hamcrest-core:1.3'
|
||||||
|
testCompile('junit:junit:4.10') {
|
||||||
|
exclude group: 'org.hamcrest', module: 'hamcrest-core'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 21
|
||||||
|
buildToolsVersion '21.1.2'
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 15
|
||||||
|
targetSdkVersion 21
|
||||||
|
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
testCoverageEnabled rootProject.testCoverage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
abortOnError true
|
||||||
|
warningsAsErrors true
|
||||||
|
lintConfig file("$rootProject.projectDir/config/lint/lint.xml")
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_6
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_6
|
||||||
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/DEPENDENCIES'
|
||||||
|
exclude 'META-INF/LICENSE'
|
||||||
|
exclude 'META-INF/LICENSE.txt'
|
||||||
|
exclude 'META-INF/NOTICE'
|
||||||
|
exclude 'META-INF/NOTICE.txt'
|
||||||
|
exclude 'LICENSE.txt'
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,418 @@
|
|||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.Message.RecipientType;
|
||||||
|
import com.fsck.k9.mail.internet.BinaryTempFileBody;
|
||||||
|
import com.fsck.k9.mail.internet.BinaryTempFileMessageBody;
|
||||||
|
import com.fsck.k9.mail.internet.CharsetSupport;
|
||||||
|
import com.fsck.k9.mail.internet.MimeBodyPart;
|
||||||
|
import com.fsck.k9.mail.internet.MimeHeader;
|
||||||
|
import com.fsck.k9.mail.internet.MimeMessage;
|
||||||
|
import com.fsck.k9.mail.internet.MimeMessageHelper;
|
||||||
|
import com.fsck.k9.mail.internet.MimeMultipart;
|
||||||
|
import com.fsck.k9.mail.internet.TextBody;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.james.mime4j.util.MimeUtil;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class MessageTest {
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String EIGHT_BIT_RESULT =
|
||||||
|
"From: from@example.com\r\n"
|
||||||
|
+ "To: to@example.com\r\n"
|
||||||
|
+ "Subject: Test Message\r\n"
|
||||||
|
+ "Date: Wed, 28 Aug 2013 08:51:09 -0400\r\n"
|
||||||
|
+ "MIME-Version: 1.0\r\n"
|
||||||
|
+ "Content-Type: multipart/mixed; boundary=\"----Boundary103\"\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary103\r\n"
|
||||||
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Testing.\r\n"
|
||||||
|
+ "This is a text body with some greek characters.\r\n"
|
||||||
|
+ "αβγδεζηθ\r\n"
|
||||||
|
+ "End of test.\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary103\r\n"
|
||||||
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Testing=2E\r\n"
|
||||||
|
+ "This is a text body with some greek characters=2E\r\n"
|
||||||
|
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||||
|
+ "End of test=2E\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary103\r\n"
|
||||||
|
+ "Content-Type: application/octet-stream\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: base64\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary103\r\n"
|
||||||
|
+ "Content-Type: message/rfc822\r\n"
|
||||||
|
+ "Content-Disposition: attachment\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "From: from@example.com\r\n"
|
||||||
|
+ "To: to@example.com\r\n"
|
||||||
|
+ "Subject: Test Message\r\n"
|
||||||
|
+ "Date: Wed, 28 Aug 2013 08:51:09 -0400\r\n"
|
||||||
|
+ "MIME-Version: 1.0\r\n"
|
||||||
|
+ "Content-Type: multipart/mixed; boundary=\"----Boundary102\"\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary102\r\n"
|
||||||
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Testing.\r\n"
|
||||||
|
+ "This is a text body with some greek characters.\r\n"
|
||||||
|
+ "αβγδεζηθ\r\n"
|
||||||
|
+ "End of test.\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary102\r\n"
|
||||||
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Testing=2E\r\n"
|
||||||
|
+ "This is a text body with some greek characters=2E\r\n"
|
||||||
|
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||||
|
+ "End of test=2E\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary102\r\n"
|
||||||
|
+ "Content-Type: application/octet-stream\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: base64\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary102\r\n"
|
||||||
|
+ "Content-Type: message/rfc822\r\n"
|
||||||
|
+ "Content-Disposition: attachment\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "From: from@example.com\r\n"
|
||||||
|
+ "To: to@example.com\r\n"
|
||||||
|
+ "Subject: Test Message\r\n"
|
||||||
|
+ "Date: Wed, 28 Aug 2013 08:51:09 -0400\r\n"
|
||||||
|
+ "MIME-Version: 1.0\r\n"
|
||||||
|
+ "Content-Type: multipart/mixed; boundary=\"----Boundary101\"\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary101\r\n"
|
||||||
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Testing.\r\n"
|
||||||
|
+ "This is a text body with some greek characters.\r\n"
|
||||||
|
+ "αβγδεζηθ\r\n"
|
||||||
|
+ "End of test.\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary101\r\n"
|
||||||
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Testing=2E\r\n"
|
||||||
|
+ "This is a text body with some greek characters=2E\r\n"
|
||||||
|
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||||
|
+ "End of test=2E\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary101\r\n"
|
||||||
|
+ "Content-Type: application/octet-stream\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: base64\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary101--\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary102--\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary103--\r\n";
|
||||||
|
|
||||||
|
private static final String SEVEN_BIT_RESULT =
|
||||||
|
"From: from@example.com\r\n"
|
||||||
|
+ "To: to@example.com\r\n"
|
||||||
|
+ "Subject: Test Message\r\n"
|
||||||
|
+ "Date: Wed, 28 Aug 2013 08:51:09 -0400\r\n"
|
||||||
|
+ "MIME-Version: 1.0\r\n"
|
||||||
|
+ "Content-Type: multipart/mixed; boundary=\"----Boundary103\"\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: 7bit\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary103\r\n"
|
||||||
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Testing=2E\r\n"
|
||||||
|
+ "This is a text body with some greek characters=2E\r\n"
|
||||||
|
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||||
|
+ "End of test=2E\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary103\r\n"
|
||||||
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Testing=2E\r\n"
|
||||||
|
+ "This is a text body with some greek characters=2E\r\n"
|
||||||
|
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||||
|
+ "End of test=2E\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary103\r\n"
|
||||||
|
+ "Content-Type: application/octet-stream\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: base64\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary103\r\n"
|
||||||
|
+ "Content-Type: message/rfc822\r\n"
|
||||||
|
+ "Content-Disposition: attachment\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: 7bit\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "From: from@example.com\r\n"
|
||||||
|
+ "To: to@example.com\r\n"
|
||||||
|
+ "Subject: Test Message\r\n"
|
||||||
|
+ "Date: Wed, 28 Aug 2013 08:51:09 -0400\r\n"
|
||||||
|
+ "MIME-Version: 1.0\r\n"
|
||||||
|
+ "Content-Type: multipart/mixed; boundary=\"----Boundary102\"\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: 7bit\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary102\r\n"
|
||||||
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Testing=2E\r\n"
|
||||||
|
+ "This is a text body with some greek characters=2E\r\n"
|
||||||
|
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||||
|
+ "End of test=2E\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary102\r\n"
|
||||||
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Testing=2E\r\n"
|
||||||
|
+ "This is a text body with some greek characters=2E\r\n"
|
||||||
|
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||||
|
+ "End of test=2E\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary102\r\n"
|
||||||
|
+ "Content-Type: application/octet-stream\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: base64\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary102\r\n"
|
||||||
|
+ "Content-Type: message/rfc822\r\n"
|
||||||
|
+ "Content-Disposition: attachment\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: 7bit\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "From: from@example.com\r\n"
|
||||||
|
+ "To: to@example.com\r\n"
|
||||||
|
+ "Subject: Test Message\r\n"
|
||||||
|
+ "Date: Wed, 28 Aug 2013 08:51:09 -0400\r\n"
|
||||||
|
+ "MIME-Version: 1.0\r\n"
|
||||||
|
+ "Content-Type: multipart/mixed; boundary=\"----Boundary101\"\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: 7bit\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary101\r\n"
|
||||||
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Testing=2E\r\n"
|
||||||
|
+ "This is a text body with some greek characters=2E\r\n"
|
||||||
|
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||||
|
+ "End of test=2E\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary101\r\n"
|
||||||
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Testing=2E\r\n"
|
||||||
|
+ "This is a text body with some greek characters=2E\r\n"
|
||||||
|
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||||
|
+ "End of test=2E\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary101\r\n"
|
||||||
|
+ "Content-Type: application/octet-stream\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: base64\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary101--\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary102--\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "------Boundary103--\r\n";
|
||||||
|
|
||||||
|
private int mMimeBoundary;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetSendDateSetsSentDate() throws Exception {
|
||||||
|
Message message = sampleMessage();
|
||||||
|
final int milliseconds = 0;
|
||||||
|
Date date = new Date(milliseconds);
|
||||||
|
message.setSentDate(date, false);
|
||||||
|
Date sentDate = message.getSentDate();
|
||||||
|
assertNotNull(sentDate);
|
||||||
|
assertEquals(milliseconds, sentDate.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetSendDateFormatsHeaderCorrectlyWithCurrentTimeZone() throws Exception {
|
||||||
|
Message message = sampleMessage();
|
||||||
|
message.setSentDate(new Date(0), false);
|
||||||
|
assertEquals("Thu, 01 Jan 1970 09:00:00 +0900", message.getHeader("Date")[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetSendDateFormatsHeaderCorrectlyWithoutTimeZone() throws Exception {
|
||||||
|
Message message = sampleMessage();
|
||||||
|
message.setSentDate(new Date(0), true);
|
||||||
|
assertEquals("Thu, 01 Jan 1970 00:00:00 +0000", message.getHeader("Date")[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMessage() throws MessagingException, IOException {
|
||||||
|
MimeMessage message;
|
||||||
|
ByteArrayOutputStream out;
|
||||||
|
|
||||||
|
BinaryTempFileBody.setTempDirectory(InstrumentationRegistry.getTargetContext().getCacheDir());
|
||||||
|
|
||||||
|
mMimeBoundary = 101;
|
||||||
|
message = nestedMessage(nestedMessage(sampleMessage()));
|
||||||
|
out = new ByteArrayOutputStream();
|
||||||
|
message.writeTo(out);
|
||||||
|
assertEquals(EIGHT_BIT_RESULT, out.toString());
|
||||||
|
|
||||||
|
mMimeBoundary = 101;
|
||||||
|
message = nestedMessage(nestedMessage(sampleMessage()));
|
||||||
|
message.setUsing7bitTransport();
|
||||||
|
out = new ByteArrayOutputStream();
|
||||||
|
message.writeTo(out);
|
||||||
|
assertEquals(SEVEN_BIT_RESULT, out.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private MimeMessage nestedMessage(MimeMessage subMessage)
|
||||||
|
throws MessagingException, IOException {
|
||||||
|
BinaryTempFileMessageBody tempMessageBody = new BinaryTempFileMessageBody(MimeUtil.ENC_8BIT);
|
||||||
|
|
||||||
|
OutputStream out = tempMessageBody.getOutputStream();
|
||||||
|
try {
|
||||||
|
subMessage.writeTo(out);
|
||||||
|
} finally {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
MimeBodyPart bodyPart = new MimeBodyPart(tempMessageBody, "message/rfc822");
|
||||||
|
bodyPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, "attachment");
|
||||||
|
bodyPart.setEncoding(MimeUtil.ENC_8BIT);
|
||||||
|
|
||||||
|
MimeMessage parentMessage = sampleMessage();
|
||||||
|
((Multipart) parentMessage.getBody()).addBodyPart(bodyPart);
|
||||||
|
|
||||||
|
return parentMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MimeMessage sampleMessage() throws MessagingException, IOException {
|
||||||
|
MimeMessage message = new MimeMessage();
|
||||||
|
message.setFrom(new Address("from@example.com"));
|
||||||
|
message.setRecipient(RecipientType.TO, new Address("to@example.com"));
|
||||||
|
message.setSubject("Test Message");
|
||||||
|
message.setHeader("Date", "Wed, 28 Aug 2013 08:51:09 -0400");
|
||||||
|
message.setEncoding(MimeUtil.ENC_8BIT);
|
||||||
|
|
||||||
|
NonRandomMimeMultipartTest multipartBody = new NonRandomMimeMultipartTest();
|
||||||
|
multipartBody.setSubType("mixed");
|
||||||
|
multipartBody.addBodyPart(textBodyPart(MimeUtil.ENC_8BIT));
|
||||||
|
multipartBody.addBodyPart(textBodyPart(MimeUtil.ENC_QUOTED_PRINTABLE));
|
||||||
|
multipartBody.addBodyPart(binaryBodyPart());
|
||||||
|
MimeMessageHelper.setBody(message, multipartBody);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MimeBodyPart binaryBodyPart() throws IOException,
|
||||||
|
MessagingException {
|
||||||
|
String encodedTestString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
+ "abcdefghijklmnopqrstuvwxyz0123456789+/\r\n";
|
||||||
|
|
||||||
|
BinaryTempFileBody tempFileBody = new BinaryTempFileBody(MimeUtil.ENC_BASE64);
|
||||||
|
|
||||||
|
InputStream in = new ByteArrayInputStream(
|
||||||
|
encodedTestString.getBytes("UTF-8"));
|
||||||
|
|
||||||
|
OutputStream out = tempFileBody.getOutputStream();
|
||||||
|
try {
|
||||||
|
IOUtils.copy(in, out);
|
||||||
|
} finally {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
MimeBodyPart bodyPart = new MimeBodyPart(tempFileBody,
|
||||||
|
"application/octet-stream");
|
||||||
|
bodyPart.setEncoding(MimeUtil.ENC_BASE64);
|
||||||
|
|
||||||
|
return bodyPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MimeBodyPart textBodyPart(String encoding)
|
||||||
|
throws MessagingException {
|
||||||
|
TextBody textBody = new TextBody(
|
||||||
|
"Testing.\r\n"
|
||||||
|
+ "This is a text body with some greek characters.\r\n"
|
||||||
|
+ "αβγδεζηθ\r\n"
|
||||||
|
+ "End of test.\r\n");
|
||||||
|
textBody.setCharset("utf-8");
|
||||||
|
MimeBodyPart bodyPart = new MimeBodyPart(textBody, "text/plain");
|
||||||
|
CharsetSupport.setCharset("utf-8", bodyPart);
|
||||||
|
bodyPart.setEncoding(encoding);
|
||||||
|
return bodyPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NonRandomMimeMultipartTest extends MimeMultipart {
|
||||||
|
|
||||||
|
public NonRandomMimeMultipartTest() throws MessagingException {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateBoundary() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("----Boundary");
|
||||||
|
sb.append(Integer.toString(mMimeBoundary++));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,276 @@
|
|||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.internet.BinaryTempFileBody;
|
||||||
|
import com.fsck.k9.mail.internet.MimeMessage;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.spongycastle.openpgp.PGPCompressedData;
|
||||||
|
import org.spongycastle.openpgp.PGPException;
|
||||||
|
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||||
|
import org.spongycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.spongycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
|
import org.spongycastle.openpgp.PGPSignature;
|
||||||
|
import org.spongycastle.openpgp.PGPSignatureList;
|
||||||
|
import org.spongycastle.openpgp.PGPUtil;
|
||||||
|
import org.spongycastle.openpgp.bc.BcPGPObjectFactory;
|
||||||
|
import org.spongycastle.openpgp.bc.BcPGPPublicKeyRingCollection;
|
||||||
|
import org.spongycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.assertTrue;
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class PgpMimeMessageTest {
|
||||||
|
private static final String PUBLIC_KEY = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||||
|
"Version: GnuPG v1\n" +
|
||||||
|
"\n" +
|
||||||
|
"mQINBE49+OsBEADIu2zVIYllkqLYaCZq2d8r80titzegJiXTaW8fRS0FKGE7KmNt\n" +
|
||||||
|
"tWvWdiyLqvWlP4Py9OZPmEBdz8AaPxqCFmVZfJimf28CW0wz2sRCYmmbQqaHFfpD\n" +
|
||||||
|
"rK+EJofckOu2j81coaFVLbvkvUNhWU7/DKyv4+EBFt9fjxptbfpNKttwI0aeUVCa\n" +
|
||||||
|
"+Z/m18+OLpeE33BXd5POrBb4edAlMCwKk8m4nDXJ3B+KmR0qfCLB79gqEjsDLl+y\n" +
|
||||||
|
"65NcRk5uxIk53NRXHkmQujX1bsf5VFLha4KbUaB7BCtcSi1rY99WXfO/PWzTelOh\n" +
|
||||||
|
"pKDIRq+v3Kl21TipY0t4kco4AUlIx5b1F0EHPpmIDr0gEheZBali5c9wUR8czc/H\n" +
|
||||||
|
"aNkRP81hTPeBtUqp1S7GtJfcuWv6dyfBBVlnev98PCKOJo05meVwf3hkOLrciTfo\n" +
|
||||||
|
"1yuy/9hF18u3GhL8HLrxMQksLhD6sPzDto4jJQDxKAa7v9aLoR7oIdeWkn1TU61E\n" +
|
||||||
|
"ODR/254BRMoq619hqJwSNt6yOjGT2BBvlwbKdS8Xfw7SsBGGW8WnVJrqFCusfjSm\n" +
|
||||||
|
"DBdV/KWstRnOMqw4nhAwNFfXmAL2L8a+rLHxalFggfGcvVpzDhJyTg+/R1y3JMCo\n" +
|
||||||
|
"FfdFuhOTfkMqjGx8FgTmINOt54Wf9Xg6W0hQh3i98Wza3n8NuSPQJtAdqQARAQAB\n" +
|
||||||
|
"tBVja2V0dGkgPGNrQGNrZXR0aS5kZT6JAhwEEAECAAYFAk+6naAACgkQctTBoSHq\n" +
|
||||||
|
"3aHS+g/+MNxxfoEK+zopjWgvJmigOvejIpBWsLYJOJrpOgQuA61dQnQg0eLXPMDc\n" +
|
||||||
|
"xQTrPtIlkn7idtLbaG2FScheOS0RdApL8UJTiU18dzjHUWsLLhEFhOAgw/kqcdG0\n" +
|
||||||
|
"A95apNucybWU9jxynN9arxU6U+HZ67/JKxRjfdPxm+CmjiQwFPU9d6kGU/D08y58\n" +
|
||||||
|
"1VIn7IopHlbqOYRuQcX0p6Q642oRBp4b6+ggov21mgqscKe/eBQ8yUxf61eywLbb\n" +
|
||||||
|
"On63fkF1vl/RvsVcOnxcPLxUH4vmhuGPJ546RN7CCNjVF0QvuH9R8dnxS7/+rLe7\n" +
|
||||||
|
"BVtZ/8sAy9r8LvnehZWVww4Wo9haVQxB69+ns63lEb+dzbBmsKbGvQ98S/Hs62Wj\n" +
|
||||||
|
"nkMy7k+xzoRMa7tbKEtwwppxJVVSW//CVvEsS7DyaZna0udLh16MBCbMDzfAa3T4\n" +
|
||||||
|
"PmgQPmV1BeysHcFOn3p6p2ZRcQGEdvMBYUjqxxExstwZEY8nGagvG7j5YCJKzBNY\n" +
|
||||||
|
"xdBwkHXU3R3iM9o4aCKBsG2DMGHyhkHJXuGv9jFM32tAAf36qUJZ9eTKtoUt4xGt\n" +
|
||||||
|
"LuxgnkS830c7nZBfJARro75SDG9eew91u2aIDGO3aNXeOODrYl2KOWbpXg/NJDwS\n" +
|
||||||
|
"mlUZdwInb0PL6EDij1NtDiap2sIBKxtDjAeilS6vwS8s2P9HZdqJAkEEEwECACsC\n" +
|
||||||
|
"GyMFCRLMAwAGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheABQJOPftbAhkBAAoJEO4v\n" +
|
||||||
|
"7zp9qOKJG+oP/RBN5ahJCpwrk7U05J8x7UOPuP4UElMYoPYZCSp55mH6Xmr2C626\n" +
|
||||||
|
"DvTxhElz1WY7oIOJ7Mgp1RtGqZYV52d6fER10jowGbSkiFTvKb4PhQl4+AcGODMY\n" +
|
||||||
|
"LRVBw90rRhDSXzBQMeiyUf7Wse1jPsBfuOe6V1TsAtqjaAzrUDQOcsjQqW5ezvIj\n" +
|
||||||
|
"GNTFunX6wMUHzSBX6Lh0fLAp5ICp+l3agJ8S41Y4tSuFVil2IRX3o4vqxvU4f0C+\n" +
|
||||||
|
"KDIeJriLAMHajUp0V6VdisRHujjoTkZAGogJhNmNg0YH191a7AAKvVePgMQ/fsoW\n" +
|
||||||
|
"1hm9afwth/HOKvMx8fgKMwkn004V/to7qHByWDND33rgwlv1LYuvumEFd/paIABh\n" +
|
||||||
|
"dLhC6o6moVzwlOqhGfoD8DZAIzNCS4q2uCg8ik4temetPbCc5wMFtd+FO+FOb1tO\n" +
|
||||||
|
"/RahWeBfULreEijnv/zUZPetkJV9jTZXgXqCI9GCf6MTJrOLZ+G3hVxFyyHTKlWt\n" +
|
||||||
|
"iIzJHlX9rd3oQc7YJbdDFMZA+SdlGqiGdsjBmq0kcRqhhEa5QsnoNm9tuPuFnL5o\n" +
|
||||||
|
"GG7OFPztj9tr9ViRvsFBlx9jvmjRbRNF3287j1r+4lbGigsA1o8bRkLLXVSK1gCw\n" +
|
||||||
|
"bOLAPNJYH5bde6O+Qb8bepg9TByiohsFssxYXHwbgu/pcCMU1hCf15t4iQI+BBMB\n" +
|
||||||
|
"AgAoBQJOPfr+AhsjBQkSzAMABgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDu\n" +
|
||||||
|
"L+86fajiic/5EACHIaprlic0VKeV1H564KionZd7y3i3mX+e7Mdkd9QBFkb14UBw\n" +
|
||||||
|
"3RFnQhvq1MtaAC1lIYACYdIMF6/8LB1WQjB7kyt0IHbjEyodBVHq3U9n+mt+ZFy3\n" +
|
||||||
|
"6loA2r1odFJIaUWA2jBlBhtd3AQriANv0yciv4dPqPQfeAR5GxDiRbzGP1FZ47To\n" +
|
||||||
|
"PXZDHY9EKwaXo4q5D7XHzQy2aFe0IVUzXnofSE2KP9bu/wUU2DjZJ4cVXFdGFv5D\n" +
|
||||||
|
"xQ48UgXfhmPXSx1eeElDWdZHhH8BI7DOL66+FKm9PLiDYHUuVTvPxFSppu/+Gw5p\n" +
|
||||||
|
"gqDBwWEeKtJ1Yf3a5Vvbt+EK8BgC1/KaqY7A++dD2vM7w8PIKcf57WXF4O6KkIiW\n" +
|
||||||
|
"0M36eoAqAyuwqeTh3+mCWewegQBS2wORBYipbDf9OPTj/fsyCkaaXM2/wee79m+W\n" +
|
||||||
|
"+/67HVYlpIJPIKJIGs1N0PTl8WYZdaMLSL7nU/y3j51ytdidiKvRWl5X3MaCpp07\n" +
|
||||||
|
"T8MSogntMxXLU2zEnUqJjykXVpavFfXi1piw98qd+5wKMwiGLRq52z73N+q5nWk+\n" +
|
||||||
|
"5B2gqA3soXvloxXmoVuoTZDSnTjuQZk1kVl2XA+enE5rjVzpGte56QRYOGrjI9II\n" +
|
||||||
|
"SjH/PYLKSwjw8YzTeYFrv5UHegjU1G7auq5nJLsCupxADoRBw2y99Oiyg7QeY2tl\n" +
|
||||||
|
"dHRpIDxja2V0dGlAZ29vZ2xlbWFpbC5jb20+iQIcBBABAgAGBQJPup3IAAoJEHLU\n" +
|
||||||
|
"waEh6t2h1EoP/1Uw+cWK2lJU2BTwWuSTgL/SPoFoR+UKWQ7fES4eTZ330hHmWb4V\n" +
|
||||||
|
"Xpg+ZR6QYhXnJxMOMZ2tnya95GgdMJ+Hd4vlq6qb8746wmzIOt5XjhdMr3yiUsY9\n" +
|
||||||
|
"NC6P6ymuYEwuNMQBU/Z53rpuoFaF4Wc9nycK+3Gj6t3aPU0JX+qiFJl63+8GNw/Q\n" +
|
||||||
|
"CL+JQ4URQB3Vw/RADZfTBbT3VmrdSLGX2/I+nm64ysXvn6nt3q1JTHWXapPGrJXi\n" +
|
||||||
|
"HTlvjg+Niw38iBeHOkZ9Td5BIPBlj/8SXy9weG55ruTJFw0SXhV3VXIGbN0ZuJ3g\n" +
|
||||||
|
"nsusNCo4pJrFvJ0j3hzYrgOf/8jRUeesu7HlUPnYdBiJTNgKdCh5LlrKXlaisobl\n" +
|
||||||
|
"H33aufjO6i5HrX+/b1U9wE/G7MIzopcgiaeSYSJpO9huBJ0+Jri/4tdxvgT6aeNz\n" +
|
||||||
|
"9uL4rQKH2gUr9E89Np4aZ3zpp1QxfoJTVaR5AyJNaiiDOvZbvELYXK6QjAwgXIVr\n" +
|
||||||
|
"ScopPOXL1E+fdV9tsvYJfTbTJLZ9qeMRIOBPyhSbiDrB4r/i5zYyfydeEFVxackY\n" +
|
||||||
|
"vgSp++5HZt5lG0LFVjNnaPZETVCgVb5wmCxNsDqYV1fuxlAmPlTuXfMAvr+bxU/z\n" +
|
||||||
|
"3dmBDc7X1VfJVLzb0M5Z0KqvQlWTZkAkIPdQarJchvOBnFa7Rb6qFpcAiQI+BBMB\n" +
|
||||||
|
"AgAoAhsjBQkSzAMABgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAUCTj37WgAKCRDu\n" +
|
||||||
|
"L+86fajiiXgTD/9tA3FTGjiCE4Z0Nzi/Q+jmNJXGr/MQvgSlbKTGKJKkNk64kLTu\n" +
|
||||||
|
"HhYdbNhj/8419fINhxOzbetdWi+RUIRqk/FstBNGCbFYwNBbhp7jSToHLw1oESoN\n" +
|
||||||
|
"zPhxkuptvjyaEjrn50ydykVdTeMjytmZ3w7iu5eOt+tNS0x0thGfM3a4kdYoKW0v\n" +
|
||||||
|
"mp2BmrtUAXXsOJ475EK6IXeoGLMbgA+JtiDnWH12t/Dfl7L/6Nxjk1fGlihcJl6P\n" +
|
||||||
|
"Z1ZytDuRjnvlt77nqMaka7N+GadqmPUWonhKg/aGPMEgQUD4IWM/2Y2EpJIqVfB5\n" +
|
||||||
|
"Dv7llScCRB8mte/T8/dvpgr5B0KqGJDudb7Cgp+8zDGCU+M3uHU5ZQRlBO3bbCML\n" +
|
||||||
|
"nwT6BxmLT/6ufW7nT1eXscDi+DFKsLa6FQmDY38tzB6tyYlHxQU3RTkm4cLfDzI8\n" +
|
||||||
|
"/0JPRfx/RlKLW39QEmFJySMB3IVRtp5R0KNoKaAtYb5hRvD2JJJnx5q0u3h+me6j\n" +
|
||||||
|
"RzCMPJWxRKQjx0MdKEJedAH02XEqgeTunm7Kitb3aYuSykHUt2D/fgA4/CQoThF5\n" +
|
||||||
|
"SYUVbviYToEu/1hQAeHe9S1F92jCrjuTUmqejoVotk5O3uHBr7A3ASOoBrdaXxuS\n" +
|
||||||
|
"x9WpcRprfdtoD36TDWsSuarNxFVzcGFDaV2yN6mIf2LXTNgw2UAOHJzUqokCPgQT\n" +
|
||||||
|
"AQIAKAUCTj346wIbIwUJEswDAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ\n" +
|
||||||
|
"7i/vOn2o4onPNxAAq6jqkpbx0g/UIdNR2Q7mGQ0QJNbkt2P8Vq7jqwUu4td6GJdC\n" +
|
||||||
|
"vy4+RUWo5aRNQ3NgzRFkjLxIrTeSfK+yjruk01r3naGh6h0rk/EY1RCw1sA6GHVV\n" +
|
||||||
|
"gFcf83JtfgxH4NE8br+eiNnMODhOXG/UJsBMNo8bfyZu3FnJdUebCODMACJimKWb\n" +
|
||||||
|
"gBXa5EOnDZzXjYQrNRt95/yHse76V8JLdHqSnYPvVwcIT6MubF2NPspSFjfnFsj9\n" +
|
||||||
|
"J1Fb6aiI+3ob6HJNt2kyN0CdnnR/ZEZun8KQ37jJy7f5LXI6FDDT52oPBfddRRwy\n" +
|
||||||
|
"qZsmprbQjxUdIPKAYyjIELy+iAoFTrsJYvGNrgGMHI2ecyC2TE3uJ3qFALLhkFAS\n" +
|
||||||
|
"xYR+sSjAI3nJHPcfsfg10clrCfhh1KDWJjlVGgFjNd0MKIhLKA4kfwQvU4BSr5Al\n" +
|
||||||
|
"3fzflkRQuLDTNEeM9fwVW6ew+7IHpBNmYtnkSbmURcZoA4y8VuHH7qHID756kf4W\n" +
|
||||||
|
"u+wfNLf0SUZ1061y+PI77wUPUEVI2uJzo0xuHMG+L0TitRUv0zvaIGFt9ClX03FU\n" +
|
||||||
|
"6r1PPLGG1JNWuBORNgTJVIQzhLM3du7OnCdc4NhfOqZUfdWrIbgPEc870DnQSdmn\n" +
|
||||||
|
"J9OTF082SXEfEbjYzLuS5/aImXENypp6A7zeHBJ+TBJUNQj0c7S1qBeQGey0IUNo\n" +
|
||||||
|
"cmlzdGlhbiBLZXR0ZXJlciA8Y2tAY2tldHRpLmRlPokCPgQTAQIAKAUCU/eh2wIb\n" +
|
||||||
|
"IwUJEswDAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ7i/vOn2o4omSGg/9\n" +
|
||||||
|
"EIj+Zz5rqC9BOC3sxbvyvZaPz0G6gT36i0ZW9Qe1drqxs7rcUelYPii8TPB/4v+H\n" +
|
||||||
|
"cx82qQpSnD6X7e8hNuRgsulkgZIhT/jnBFEJJoyMtt25UZIolj4JFpw1g+PRkufu\n" +
|
||||||
|
"KlVZCisJup+fFN2O6IcsfFqXxnaITWalFYMvOXwJ9rbT+2kczsH+MnxqeRvFQw7u\n" +
|
||||||
|
"Gy7Q3bu+A9+rntBhz/LPdzBOJBJh672Y9f9UVsmEFB66d84l7yUs1vlzi9DAqJK4\n" +
|
||||||
|
"y49hCe9QMv+NwL9rB9QxLdrbX724IRvGVwRzufd5jHOgAsYbizO+QltBJTsmRHvi\n" +
|
||||||
|
"yKClxuiUE4ygQyd5TT3ATC8wQKGfAGWRWaZoi3X6wWvKvW8cPg8ilMoTtPTlZuPL\n" +
|
||||||
|
"G32n0NaD7dacpmKfaLeopPAJgrnTl9LwPEDg4dwcSK+ETCY1BcoVGtOVxH1ghMd2\n" +
|
||||||
|
"IYOX+BSJiG39ApiHHBwPtc/PIqPjtR7MGB6dCldZZ46eHleCB8Re5HPrQAok+ijb\n" +
|
||||||
|
"XX0gx7ACYTniH+TsFszZyuLGstR8Cs8s7MwnbAX40506lDrj9c+0FE69/rJIMQsc\n" +
|
||||||
|
"wauGk1x1UaK2+gzBw28ymilhBbuOFabuStAHfGx/1niJMgBO4BiOPIBTjMOYtARR\n" +
|
||||||
|
"OSZ9dNGXkKYnxtN6T/kTO3F5N/fFJ42WjDWbrvfqDSy5Ag0ETj346wEQALEnw5y/\n" +
|
||||||
|
"zL3QAug9xuHktdVKCbxwAy8Q1ei5UA/GTGnTLdsHIN5e1B2bJyZaYcPTIT+xNgzP\n" +
|
||||||
|
"hwDQTosFFpg/JLP1xI28mShk8ai3ls73EhJLUGazOZ0ujxyMkWD0rIBMee6YkQMG\n" +
|
||||||
|
"zUkJKaEtqeVLci67Q8QLHLfE331JyTtd0gwlps6FAd7PuCl/50cayr0yXMx67iwK\n" +
|
||||||
|
"kyvXaLHYUjdK13MC2xoc4VrirzfNtX0JtCmAYoJ2i2Yq7vgLQasUjbzUsLUuwhol\n" +
|
||||||
|
"yoxwE6lB6paBdTh1dTa4mCN3Y8gM+CMveqQUcZuOyFZDWNtMPPCNeWWRkKgfc+fw\n" +
|
||||||
|
"HSiCHhDWu/7S6/xSqDb3qegXm6cAA2WFxJ+oEwTSRvK/89y6T3oiFbjmZs+sSRjr\n" +
|
||||||
|
"ZAsE3rDC2WFRUFBq6/V7+eO2F1fqNLPzXOaVQX9i3BHv4XjxC0PQoVFnvpSJlHSW\n" +
|
||||||
|
"Vuw5xA3Qqa8GuB80zWEqVBJ30gfqj1BAErpKwaVKJOuvRuQa2wkq7iXO/Io4S7UQ\n" +
|
||||||
|
"HFO+U9W87PaPNdfjxxEsVmexeXhF8l5zwHYyqKK0Pch/YDoUk/+w7Jn3cpmpceim\n" +
|
||||||
|
"YVEDr/YqrbvLpakHuEQiDgWZmcHHEVA7DbfsOULqq1vnpVq0TictdZ20Z8MJ2gAM\n" +
|
||||||
|
"P9HCZHPxLafI3YqQrXR3UIHb48Zwy9tdMv7NABEBAAGJAiUEGAECAA8FAk49+OsC\n" +
|
||||||
|
"GwwFCRLMAwAACgkQ7i/vOn2o4okF+BAAkN0Kd404HPy/35mCCdWm5DHpcxEURoY1\n" +
|
||||||
|
"X6mv6D+pvPQHUN9GKeYYT6wjcpsDsCn2UX9mp0e24SXOxZoVlJ7T6L/QN+MUwnt2\n" +
|
||||||
|
"LAO9XCZLMijhe7KX51FJjld1W9XfauqhPlR1Lzr9cJI3UdiYcsZH3X6SfW/hLLRE\n" +
|
||||||
|
"MWm/3YfACVVWNkG9PanhroNcVr925k/y58WRKdJOOgMGGBYyIAvtWb6m0Qn978AE\n" +
|
||||||
|
"53r7msHwZq06sPXIZJpCl6CTeyMrqU90G+JJY3BfP9rFsU9OLkDRrsAELleI9iXP\n" +
|
||||||
|
"QGw6Ixezdi93CqY+Y4weCjtYxm/5vKxwssg/ALVkM/VftWgWRSnZmnZwubgBzgwy\n" +
|
||||||
|
"wBwGHxPHz7CV3lBKZfw8U3L4Md3u1bMUu6Y+jW+322D+7+ZaLdJejmmJcEvLaItd\n" +
|
||||||
|
"c60IHTM/GbtV7TDiqQaRmyLY5KxnwGLthcYUsGI7HYDNqEa1+cRctB8lEWpgTjHK\n" +
|
||||||
|
"nwemvB5c1fPxao7w15O0tvSCX2kD5UMoAbvWJJvxcUTPTPBEHTYWrAk+Ny7CbdMA\n" +
|
||||||
|
"+71r942RXo9Xdm4hqjfMcDXdQmfjftfFB1rsBd5Qui8ideQP7ypllsWC8fJUkWN6\n" +
|
||||||
|
"3leW5gysLx9Mj6bu6XB4rYS1zH2keGtZe4Qqlxss7JPVsJzD9xSotg+G/Wb7F3HL\n" +
|
||||||
|
"HzpeeqkwzVU=\n" +
|
||||||
|
"=3yEX\n" +
|
||||||
|
"-----END PGP PUBLIC KEY BLOCK-----\n";
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSignedMessage() throws IOException, MessagingException, PGPException {
|
||||||
|
String messageSource = "Date: Mon, 08 Dec 2014 17:44:18 +0100\r\n" +
|
||||||
|
"From: cketti <cketti@googlemail.com>\r\n" +
|
||||||
|
"MIME-Version: 1.0\r\n" +
|
||||||
|
"To: test@example.com\r\n" +
|
||||||
|
"Subject: OpenPGP signature test\r\n" +
|
||||||
|
"Content-Type: multipart/signed; micalg=pgp-sha1;\r\n" +
|
||||||
|
" protocol=\"application/pgp-signature\";\r\n" +
|
||||||
|
" boundary=\"24Bem7EnUI1Ipn9jNXuLgsetqa6wOkIxM\"\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"This is an OpenPGP/MIME signed message (RFC 4880 and 3156)\r\n" +
|
||||||
|
"--24Bem7EnUI1Ipn9jNXuLgsetqa6wOkIxM\r\n" +
|
||||||
|
"Content-Type: multipart/mixed;\r\n" +
|
||||||
|
" boundary=\"------------030308060900040601010501\"\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"This is a multi-part message in MIME format.\r\n" +
|
||||||
|
"--------------030308060900040601010501\r\n" +
|
||||||
|
"Content-Type: text/plain; charset=utf-8\r\n" +
|
||||||
|
"Content-Transfer-Encoding: quoted-printable\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"Message body\r\n" +
|
||||||
|
"goes here\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"--------------030308060900040601010501\r\n" +
|
||||||
|
"Content-Type: text/plain; charset=UTF-8;\r\n" +
|
||||||
|
" name=\"attachment.txt\"\r\n" +
|
||||||
|
"Content-Transfer-Encoding: base64\r\n" +
|
||||||
|
"Content-Disposition: attachment;\r\n" +
|
||||||
|
" filename=\"attachment.txt\"\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"VGV4dCBhdHRhY2htZW50Cg==\r\n" +
|
||||||
|
"--------------030308060900040601010501--\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"--24Bem7EnUI1Ipn9jNXuLgsetqa6wOkIxM\r\n" +
|
||||||
|
"Content-Type: application/pgp-signature; name=\"signature.asc\"\r\n" +
|
||||||
|
"Content-Description: OpenPGP digital signature\r\n" +
|
||||||
|
"Content-Disposition: attachment; filename=\"signature.asc\"\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"-----BEGIN PGP SIGNATURE-----\r\n" +
|
||||||
|
"Version: GnuPG v1\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"iQIcBAEBAgAGBQJUhdVqAAoJEO4v7zp9qOKJ8DQP/1+JE8UF7UmirnN1ZO+25hFC\r\n" +
|
||||||
|
"jAfFMxRWMWXN0gGB+6ySy6ah0bCwmRwHpRBsW/tNcsmOPKb2XBf9zwF06uk/lLp4\r\n" +
|
||||||
|
"ZmGXxSdQ9XJrlaHk8Sitn9Gi/1L+MNWgrsrLROAZv2jfc9wqN3FOrhN9NC1QXQvO\r\n" +
|
||||||
|
"+D7sMorSr3l94majoIDrzvxEnfJVfrZWNTUaulJofOJ55GBZ3UJNob1WKjrnculL\r\n" +
|
||||||
|
"IwmSERmVUoFBUfe/MBqqZH0WDJq9nt//NZFHLunj6nGsrpush1dQRcbR3zzQfXkk\r\n" +
|
||||||
|
"s7zDLDa8VUv6OxcefjsVN/O7EenoWWgNg6GfW6tY2+oUsLSP2OS3JXvYsylQP4hR\r\n" +
|
||||||
|
"iU1V9vvsu2Ax6bVb0+uTqw3jNiqVFy3o4mBigVUqp1EFIwBYmyNbe5wj4ACs9Avj\r\n" +
|
||||||
|
"9t2reFSfXobWQFUS4s71JeMefNAHHJWZI63wNTxE6LOw01YxdJiDaPWGTOyM75MK\r\n" +
|
||||||
|
"yqn7r5uIfeSv8NypGJaUv4firxKbrcZKk7Wpeh/rZuUSgoPcf3I1IzXfGKKIBHjU\r\n" +
|
||||||
|
"WUMhTF5SoC5kIZyeXvHrhTM8HszcS8EoG2XcmcYArwgCUlOunFwZNqLPsfdMTRL6\r\n" +
|
||||||
|
"9rcioaohEtroqoJiGAToJtIz8kqCaamnP/ASBkp9qqJizRd6fqt+tE8BsmJbuPLS\r\n" +
|
||||||
|
"6lBpS8j0TqmaZMYfB9u4\r\n" +
|
||||||
|
"=QvET\r\n" +
|
||||||
|
"-----END PGP SIGNATURE-----\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"--24Bem7EnUI1Ipn9jNXuLgsetqa6wOkIxM--\r\n";
|
||||||
|
|
||||||
|
BinaryTempFileBody.setTempDirectory(InstrumentationRegistry.getTargetContext().getCacheDir());
|
||||||
|
|
||||||
|
InputStream messageInputStream = new ByteArrayInputStream(messageSource.getBytes());
|
||||||
|
MimeMessage message;
|
||||||
|
try {
|
||||||
|
message = new MimeMessage(messageInputStream, true);
|
||||||
|
} finally {
|
||||||
|
messageInputStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Multipart multipartSigned = (Multipart) message.getBody();
|
||||||
|
|
||||||
|
BodyPart signedPart = multipartSigned.getBodyPart(0);
|
||||||
|
ByteArrayOutputStream signedPartOutputStream = new ByteArrayOutputStream();
|
||||||
|
signedPart.writeTo(signedPartOutputStream);
|
||||||
|
byte[] signedData = signedPartOutputStream.toByteArray();
|
||||||
|
|
||||||
|
Body signatureBody = multipartSigned.getBodyPart(1).getBody();
|
||||||
|
ByteArrayOutputStream signatureBodyOutputStream = new ByteArrayOutputStream();
|
||||||
|
signatureBody.writeTo(signatureBodyOutputStream);
|
||||||
|
byte[] signatureData = signatureBodyOutputStream.toByteArray();
|
||||||
|
|
||||||
|
assertTrue(verifySignature(signedData, signatureData));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean verifySignature(byte[] signedData, byte[] signatureData) throws IOException, PGPException {
|
||||||
|
InputStream signatureInputStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(signatureData));
|
||||||
|
PGPObjectFactory pgpObjectFactory = new BcPGPObjectFactory(signatureInputStream);
|
||||||
|
Object pgpObject = pgpObjectFactory.nextObject();
|
||||||
|
|
||||||
|
PGPSignatureList pgpSignatureList;
|
||||||
|
if (pgpObject instanceof PGPCompressedData) {
|
||||||
|
PGPCompressedData compressedData = (PGPCompressedData) pgpObject;
|
||||||
|
pgpObjectFactory = new BcPGPObjectFactory(compressedData.getDataStream());
|
||||||
|
pgpSignatureList = (PGPSignatureList) pgpObjectFactory.nextObject();
|
||||||
|
} else {
|
||||||
|
pgpSignatureList = (PGPSignatureList) pgpObject;
|
||||||
|
}
|
||||||
|
PGPSignature signature = pgpSignatureList.get(0);
|
||||||
|
|
||||||
|
InputStream keyInputStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(PUBLIC_KEY.getBytes()));
|
||||||
|
PGPPublicKeyRingCollection pgpPublicKeyRingCollection = new BcPGPPublicKeyRingCollection(keyInputStream);
|
||||||
|
PGPPublicKey publicKey = pgpPublicKeyRingCollection.getPublicKey(signature.getKeyID());
|
||||||
|
|
||||||
|
signature.init(new BcPGPContentVerifierBuilderProvider(), publicKey);
|
||||||
|
InputStream signedDataInputStream = new ByteArrayInputStream(signedData);
|
||||||
|
int ch;
|
||||||
|
while ((ch = signedDataInputStream.read()) >= 0) {
|
||||||
|
signature.update((byte) ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
signedDataInputStream.close();
|
||||||
|
keyInputStream.close();
|
||||||
|
signatureInputStream.close();
|
||||||
|
|
||||||
|
return signature.verify();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.internet.BinaryTempFileBody;
|
||||||
|
import com.fsck.k9.mail.internet.MimeMessage;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ReconstructMessageTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMessage() throws IOException, MessagingException {
|
||||||
|
String messageSource =
|
||||||
|
"From: from@example.com\r\n" +
|
||||||
|
"To: to@example.com\r\n" +
|
||||||
|
"Subject: Test Message \r\n" +
|
||||||
|
"Date: Thu, 13 Nov 2014 17:09:38 +0100\r\n" +
|
||||||
|
"Content-Type: multipart/mixed;\r\n" +
|
||||||
|
" boundary=\"----Boundary\"\r\n" +
|
||||||
|
"Content-Transfer-Encoding: 8bit\r\n" +
|
||||||
|
"MIME-Version: 1.0\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"This is a multipart MIME message.\r\n" +
|
||||||
|
"------Boundary\r\n" +
|
||||||
|
"Content-Type: text/plain; charset=utf-8\r\n" +
|
||||||
|
"Content-Transfer-Encoding: 8bit\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"Testing.\r\n" +
|
||||||
|
"This is a text body with some greek characters.\r\n" +
|
||||||
|
"αβγδεζηθ\r\n" +
|
||||||
|
"End of test.\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"------Boundary\r\n" +
|
||||||
|
"Content-Type: text/plain\r\n" +
|
||||||
|
"Content-Transfer-Encoding: base64\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"VGhpcyBpcyBhIHRl\r\n" +
|
||||||
|
"c3QgbWVzc2FnZQ==\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"------Boundary--\r\n" +
|
||||||
|
"Hi, I'm the epilogue";
|
||||||
|
|
||||||
|
BinaryTempFileBody.setTempDirectory(InstrumentationRegistry.getTargetContext().getCacheDir());
|
||||||
|
|
||||||
|
InputStream messageInputStream = new ByteArrayInputStream(messageSource.getBytes());
|
||||||
|
MimeMessage message;
|
||||||
|
try {
|
||||||
|
message = new MimeMessage(messageInputStream, true);
|
||||||
|
} finally {
|
||||||
|
messageInputStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream messageOutputStream = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
message.writeTo(messageOutputStream);
|
||||||
|
} finally {
|
||||||
|
messageOutputStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
String reconstructedMessage = new String(messageOutputStream.toByteArray());
|
||||||
|
|
||||||
|
assertEquals(messageSource, reconstructedMessage);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,350 @@
|
|||||||
|
package com.fsck.k9.mail.ssl;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.assertFalse;
|
||||||
|
import static junit.framework.Assert.assertTrue;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the functionality of {@link TrustManagerFactory}.
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class TrustManagerFactoryTest {
|
||||||
|
public static final String MATCHING_HOST = "k9.example.com";
|
||||||
|
public static final String NOT_MATCHING_HOST = "bla.example.com";
|
||||||
|
public static final int PORT1 = 993;
|
||||||
|
public static final int PORT2 = 465;
|
||||||
|
|
||||||
|
private static final String K9_EXAMPLE_COM_CERT1 =
|
||||||
|
"-----BEGIN CERTIFICATE-----\n"
|
||||||
|
+ "MIICCTCCAXICCQD/R0TV7d0C5TANBgkqhkiG9w0BAQUFADBJMQswCQYDVQQGEwJD\n"
|
||||||
|
+ "SDETMBEGA1UECBMKU29tZS1TdGF0ZTEMMAoGA1UEChMDSy05MRcwFQYDVQQDEw5r\n"
|
||||||
|
+ "OS5leGFtcGxlLmNvbTAeFw0xMTA5MDYxOTU3MzVaFw0yMTA5MDMxOTU3MzVaMEkx\n"
|
||||||
|
+ "CzAJBgNVBAYTAkNIMRMwEQYDVQQIEwpTb21lLVN0YXRlMQwwCgYDVQQKEwNLLTkx\n"
|
||||||
|
+ "FzAVBgNVBAMTDms5LmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB\n"
|
||||||
|
+ "iQKBgQCp7FvHRaQaOIu3iyB5GB0PtPCxy/bLlBxBb8p9QsMimX2Yz3SNjWVUzU5N\n"
|
||||||
|
+ "ggpXmmeGopLAnvZlhWYSx0yIGWwPB44kGK5eaYDRWav+K+XXgdNCJij1UWPSmFwZ\n"
|
||||||
|
+ "hUoNbrahco5AFw0jC1qi+3Dht6Y64nfNzTOYTcm1Pz4tqXiADQIDAQABMA0GCSqG\n"
|
||||||
|
+ "SIb3DQEBBQUAA4GBAIPsgd6fuFRojSOAcUyhaoKaY5hXJf8d7R3AYWxcAPYmn6g7\n"
|
||||||
|
+ "3Zms+f7/CH0y/tM81oBTlq9ZLbrJyLzC7vG1pqWHMNaK7miAho22IRuk+HwvL6OA\n"
|
||||||
|
+ "uH3x3W1/mH4ci268cIFVmofID0nYLTqOxBTczfYhI7q0VBUXqv/bZ+3bVMSh\n"
|
||||||
|
+ "-----END CERTIFICATE-----\n";
|
||||||
|
|
||||||
|
private static final String K9_EXAMPLE_COM_CERT2 =
|
||||||
|
"-----BEGIN CERTIFICATE-----\n"
|
||||||
|
+ "MIICCTCCAXICCQDMryqq0gZ80jANBgkqhkiG9w0BAQUFADBJMQswCQYDVQQGEwJD\n"
|
||||||
|
+ "SDETMBEGA1UECBMKU29tZS1TdGF0ZTEMMAoGA1UEChMDSy05MRcwFQYDVQQDEw5r\n"
|
||||||
|
+ "OS5leGFtcGxlLmNvbTAeFw0xMTA5MDYyMDAwNTVaFw0yMTA5MDMyMDAwNTVaMEkx\n"
|
||||||
|
+ "CzAJBgNVBAYTAkNIMRMwEQYDVQQIEwpTb21lLVN0YXRlMQwwCgYDVQQKEwNLLTkx\n"
|
||||||
|
+ "FzAVBgNVBAMTDms5LmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB\n"
|
||||||
|
+ "iQKBgQDOLzRucC3tuXL/NthnGkgTnVn03balrvYPkABvvrG83Dpp5ipIC/iPsQvw\n"
|
||||||
|
+ "pvqypSNHqrloEB7o3obQ8tiRDtbOsNQ7gKJ+YoD1drDNClV0pBvr7mvRgA2AcDpw\n"
|
||||||
|
+ "CTLKwVIyKmE+rm3vl8CWFd9CqHcYQ3Mc1KXXasN4DEAzZ/sHRwIDAQABMA0GCSqG\n"
|
||||||
|
+ "SIb3DQEBBQUAA4GBAFDcHFpmZ9SUrc0WayrKNUpSaHLRG94uzIx0VUMLROcXEEWU\n"
|
||||||
|
+ "soRw1RfoSBkcy2SEjB4CAvex6qAiOT3ubXuL+BYFav/uU8JPWZ9ovSAYqBZ9aUJo\n"
|
||||||
|
+ "G6A2hvA1lpvP97qQ/NFaGQ38XqSykZamZwSx3PlZUM/i9S9n/3MfuuXWqtLC\n"
|
||||||
|
+ "-----END CERTIFICATE-----\n";
|
||||||
|
|
||||||
|
private static final String CA_CERT =
|
||||||
|
"-----BEGIN CERTIFICATE-----\n"
|
||||||
|
+ "MIIDbTCCAlWgAwIBAgIJANCdQ+Cwnyg+MA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV\n"
|
||||||
|
+ "BAYTAkNIMRMwEQYDVQQIDApTb21lLVN0YXRlMQwwCgYDVQQKDANLLTkxGzAZBgNV\n"
|
||||||
|
+ "BAMMEnRlc3QtY2EuazltYWlsLm9yZzAeFw0xMzEyMDIxMjUwNThaFw0yMzExMzAx\n"
|
||||||
|
+ "MjUwNThaME0xCzAJBgNVBAYTAkNIMRMwEQYDVQQIDApTb21lLVN0YXRlMQwwCgYD\n"
|
||||||
|
+ "VQQKDANLLTkxGzAZBgNVBAMMEnRlc3QtY2EuazltYWlsLm9yZzCCASIwDQYJKoZI\n"
|
||||||
|
+ "hvcNAQEBBQADggEPADCCAQoCggEBAJ+YLg9enfFk5eba6B3LtQzUE7GiR2tIpQSi\n"
|
||||||
|
+ "zHMtHzn8KUnRDiGwC8VnSuWCOX7hXyQ0P6i2+DVRVBYOAeDCNMZHOq1hRqI66B33\n"
|
||||||
|
+ "QqLfkBnJAIDeLqfqlgigHs1+//7eagVA6Z38ZFre3PFuKnK9NCwS+gz7PKw/poIG\n"
|
||||||
|
+ "/FZP+ltMlkwvPww4S8SMlY6RXXH09+S/uM8aG6DUBT298eoAXTbSEIeaNhwBHZPe\n"
|
||||||
|
+ "rXqqzd8QDAIE9BFXSkh/BQiVEFDPSBMSdmUzUAsT2aM8osntnKWY5/G7B60wutvA\n"
|
||||||
|
+ "jYCULgtR6lR6jIDbG3ECHVDsTWR+Pgl+h1zeyERhN5iG1ffOtLUCAwEAAaNQME4w\n"
|
||||||
|
+ "HQYDVR0OBBYEFBlUYiTGlOu9zIPx8Q13xcnDL5QpMB8GA1UdIwQYMBaAFBlUYiTG\n"
|
||||||
|
+ "lOu9zIPx8Q13xcnDL5QpMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB\n"
|
||||||
|
+ "AJ6oC6O6I6p0vgA4+7dfyxKX745zl/fK6IVHV/GO75mLjVdyw00USbHGHAmZM5C6\n"
|
||||||
|
+ "eCKVV83m/Re5lHf8ZBjc+3rWdGCEjwyUwvDeUvzpcKF3wPxYDUOOqSI+np1cxj6q\n"
|
||||||
|
+ "6+XI5QXwyUObWtWyw1GOpLuFPbxny/TlRWvk8AfOaLANg3UhvITNZMdMHoQ2sJ3u\n"
|
||||||
|
+ "MrQ+CHe/Tal2MkwiCrYT91f3YWVaswiEAxpqxnwuSXnYyaJpqMCcA1txBDgX84FP\n"
|
||||||
|
+ "dSIM4ut+QltV2Tlx0lpH43dvttAwkPB+iL7ZF6zUki/Nq5aKyNoHOL88TACe18Lq\n"
|
||||||
|
+ "zOztD2HZfxhIz3uH2gXmqUo=\n"
|
||||||
|
+ "-----END CERTIFICATE-----\n";
|
||||||
|
|
||||||
|
private static final String CERT3 =
|
||||||
|
"-----BEGIN CERTIFICATE-----\n"
|
||||||
|
+ "MIIDjDCCAnSgAwIBAgIBATANBgkqhkiG9w0BAQUFADBNMQswCQYDVQQGEwJDSDET\n"
|
||||||
|
+ "MBEGA1UECAwKU29tZS1TdGF0ZTEMMAoGA1UECgwDSy05MRswGQYDVQQDDBJ0ZXN0\n"
|
||||||
|
+ "LWNhLms5bWFpbC5vcmcwHhcNMTMxMjAyMTMxNzEyWhcNMjMxMTMwMTMxNzEyWjBJ\n"
|
||||||
|
+ "MQswCQYDVQQGEwJDSDETMBEGA1UECAwKU29tZS1TdGF0ZTEMMAoGA1UECgwDSy05\n"
|
||||||
|
+ "MRcwFQYDVQQDDA5rOS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP\n"
|
||||||
|
+ "ADCCAQoCggEBAL9OvWtLcp6bd40Hai6A6cCmJRwn3mwcTB8E41iEQgQexqx/f9RR\n"
|
||||||
|
+ "BuQi2s80k/vXq8QU2GbwGiPkBBXMUHuiT27Lsoj8kMOnH5BXeKLaWDiMpvNqfent\n"
|
||||||
|
+ "UzBXSIOK6Yu9UtlU0MzAuYxXaunrXoS5Dejrbz743P9yW8hx7pANNU0Qfck+ekR7\n"
|
||||||
|
+ "Q4PWNgfbFHrnvcobzuFzJeWg8x9iTTsVGIaX9AVMjMUlIKvhhOWTlcTJHKzU67sp\n"
|
||||||
|
+ "OLzwH9IJ3hqwdmsgZu5D/2AZlYlpFk6AlnoxNhfy9m+T41P8+iWDYCJoxvf3d6gl\n"
|
||||||
|
+ "TlZ1FL0PzPReXeAgugyJ1qx5gJ9Vhf/rBaUCAwEAAaN7MHkwCQYDVR0TBAIwADAs\n"
|
||||||
|
+ "BglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYD\n"
|
||||||
|
+ "VR0OBBYEFPm9hbTbfmcnjjfOzrec/TrvsS5ZMB8GA1UdIwQYMBaAFBlUYiTGlOu9\n"
|
||||||
|
+ "zIPx8Q13xcnDL5QpMA0GCSqGSIb3DQEBBQUAA4IBAQAgvYQoCEklJNXBwLuWpSMx\n"
|
||||||
|
+ "CQrVxLI1XsYRzqMs0kUgM59OhwAPwdSR+UEuyXQ8QGKwSt1d//DkdhzQDATXSBYc\n"
|
||||||
|
+ "VHr16ocYPGNd/VNo7BoUCvykp3cCH3WxYYpAugXbLU8RBJzQwCM75SLQtFe20qfI\n"
|
||||||
|
+ "LErbrmKONtMk3Rfg6XtLLcaOVh1A3q13CKqDvwtZT4oo56EJOvkBkzlCvTuxJb6s\n"
|
||||||
|
+ "FD9pwROFpIN8O54C333tZzj4TDP4g9zb3sofAJ4U0osfQAXekZJdZETFGJsU6TIM\n"
|
||||||
|
+ "Dcf5/G8bZe2DnavBQfML1wI5d7NUWE8CWb95SsIvFXI0qZE0oIR+axBVl9u97uaO\n"
|
||||||
|
+ "-----END CERTIFICATE-----\n";
|
||||||
|
|
||||||
|
private static final String LINUX_COM_FIRST_PARENT_CERT =
|
||||||
|
"-----BEGIN CERTIFICATE-----\n" +
|
||||||
|
"MIIGNDCCBBygAwIBAgIBGzANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW\n" +
|
||||||
|
"MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg\n" +
|
||||||
|
"Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh\n" +
|
||||||
|
"dGlvbiBBdXRob3JpdHkwHhcNMDcxMDI0MjA1NzA5WhcNMTcxMDI0MjA1NzA5WjCB\n" +
|
||||||
|
"jDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsT\n" +
|
||||||
|
"IlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0\n" +
|
||||||
|
"YXJ0Q29tIENsYXNzIDIgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMIIB\n" +
|
||||||
|
"IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4k85L6GMmoWtCA4IPlfyiAEh\n" +
|
||||||
|
"G5SpbOK426oZGEY6UqH1D/RujOqWjJaHeRNAUS8i8gyLhw9l33F0NENVsTUJm9m8\n" +
|
||||||
|
"H/rrQtCXQHK3Q5Y9upadXVACHJuRjZzArNe7LxfXyz6CnXPrB0KSss1ks3RVG7RL\n" +
|
||||||
|
"hiEs93iHMuAW5Nq9TJXqpAp+tgoNLorPVavD5d1Bik7mb2VsskDPF125w2oLJxGE\n" +
|
||||||
|
"d2H2wnztwI14FBiZgZl1Y7foU9O6YekO+qIw80aiuckfbIBaQKwn7UhHM7BUxkYa\n" +
|
||||||
|
"8zVhwQIpkFR+ZE3EMFICgtffziFuGJHXuKuMJxe18KMBL47SLoc6PbQpZ4rEAwID\n" +
|
||||||
|
"AQABo4IBrTCCAakwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD\n" +
|
||||||
|
"VR0OBBYEFBHbI0X9VMxqcW+EigPXvvcBLyaGMB8GA1UdIwQYMBaAFE4L7xqkQFul\n" +
|
||||||
|
"F2mHMMo0aEPQQa7yMGYGCCsGAQUFBwEBBFowWDAnBggrBgEFBQcwAYYbaHR0cDov\n" +
|
||||||
|
"L29jc3Auc3RhcnRzc2wuY29tL2NhMC0GCCsGAQUFBzAChiFodHRwOi8vd3d3LnN0\n" +
|
||||||
|
"YXJ0c3NsLmNvbS9zZnNjYS5jcnQwWwYDVR0fBFQwUjAnoCWgI4YhaHR0cDovL3d3\n" +
|
||||||
|
"dy5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0\n" +
|
||||||
|
"c3NsLmNvbS9zZnNjYS5jcmwwgYAGA1UdIAR5MHcwdQYLKwYBBAGBtTcBAgEwZjAu\n" +
|
||||||
|
"BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0\n" +
|
||||||
|
"BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl\n" +
|
||||||
|
"LnBkZjANBgkqhkiG9w0BAQsFAAOCAgEAbQjxXHkqUPtUY+u8NEFcuKMDITfjvGkl\n" +
|
||||||
|
"LgrTuBW63grW+2AuDAZRo/066eNHs6QV4i5e4ujwPSR2dgggY7mOIIBmiDm2QRjF\n" +
|
||||||
|
"5CROq6zDlIdqlsFZICkuONDNFpFjaPtZRTmuK1n6gywQgCNSIrbzjPcwR/jL/wow\n" +
|
||||||
|
"bfwC9yGme1EeZRqvWy/HzFWacs7UMmWlRk6DTmpfPOPMJo5AxyTZCiCYQQeksV7x\n" +
|
||||||
|
"UAeY0kWa+y/FV+eerOPUl6yy4jRHTk7tCySxrciZwYbd6YNLmeIQoUAdRC3CH3nT\n" +
|
||||||
|
"B2/JYxltcgyGHMiPU3TtafZgLs8fvncv+wIF1YAF/OGqg8qmzoJ3ghM4upGdTMIu\n" +
|
||||||
|
"8vADdmuLC/+dnbzknxX6QEGlWA8zojLUxVhGNfIFoizu/V/DyvSvYuxzzIkPECK5\n" +
|
||||||
|
"gDoMoBTTMI/wnxXwulNPtfgF7/5AtDhA4GNAfB2SddxiNQAF7XkUHtMZ9ff3W6Xk\n" +
|
||||||
|
"FldOG+NlLFqsDBG/KLckyFK36gq+FqNFCbmtmtXBGB5L1fDIeYzcMKG6hFQxhHS0\n" +
|
||||||
|
"oqpdHhp2nWBfLlOnTNqIZNJzOH37OJE6Olk45LNFJtSrqIAZyCCfM6bQgoQvZuIa\n" +
|
||||||
|
"xs9SIp+63ZMk9TxEaQj/KteaOyfaPXI9778U7JElMTz3Bls62mslV2I1C/A73Zyq\n" +
|
||||||
|
"JZWQZ8NU4ds=\n" +
|
||||||
|
"-----END CERTIFICATE-----\n";
|
||||||
|
|
||||||
|
private static final String LINUX_COM_CERT =
|
||||||
|
"-----BEGIN CERTIFICATE-----\n" +
|
||||||
|
"MIIGhjCCBW6gAwIBAgIDAmiWMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYDVQQGEwJJ\n" +
|
||||||
|
"TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0\n" +
|
||||||
|
"YWwgQ2VydGlmaWNhdGUgU2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3Mg\n" +
|
||||||
|
"MiBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2ZXIgQ0EwHhcNMTQwODIxMjEwMDI4\n" +
|
||||||
|
"WhcNMTYwODIxMDY0NDE0WjCBlDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlm\n" +
|
||||||
|
"b3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xHTAbBgNVBAoTFFRoZSBMaW51\n" +
|
||||||
|
"eCBGb3VuZGF0aW9uMRQwEgYDVQQDFAsqLmxpbnV4LmNvbTEjMCEGCSqGSIb3DQEJ\n" +
|
||||||
|
"ARYUaG9zdG1hc3RlckBsaW51eC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n" +
|
||||||
|
"ggEKAoIBAQCjvFjOigXyqkSiVv0vz1CSDg08iilLnj8gRFRoRMA6fFWhQTp4QGLV\n" +
|
||||||
|
"1li5VMEQdZ/vyqTWjmB+FFkuTsBjFDg6gG3yw6DQBGyyM06A1dT9YKUa7LqxOxQr\n" +
|
||||||
|
"KhNOacPS/pAupAZ5jOO7fcZwIcpKcKEjjhHn7GXEVvb+K996TMA0vDYcp1lgXtil\n" +
|
||||||
|
"7Ij+1GUSA29NrnCZXUun2c5nS7OulRYcgtRyZBa13zfyaVJtEIl14ClP9gsLa/5u\n" +
|
||||||
|
"eXzZD71Jj48ZNbiKRThiUZ5FkEnljjSQa25Hr5g9DXY2JvI1r8OJOCUR8jPiRyNs\n" +
|
||||||
|
"Kgc1ZG0fibm9VoHjokUZ2aQxyQJP/C1TAgMBAAGjggLlMIIC4TAJBgNVHRMEAjAA\n" +
|
||||||
|
"MAsGA1UdDwQEAwIDqDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwHQYD\n" +
|
||||||
|
"VR0OBBYEFI0nMnIXZOz02MlXPh2g9aHesvPPMB8GA1UdIwQYMBaAFBHbI0X9VMxq\n" +
|
||||||
|
"cW+EigPXvvcBLyaGMCEGA1UdEQQaMBiCCyoubGludXguY29tgglsaW51eC5jb20w\n" +
|
||||||
|
"ggFWBgNVHSAEggFNMIIBSTAIBgZngQwBAgIwggE7BgsrBgEEAYG1NwECAzCCASow\n" +
|
||||||
|
"LgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYw\n" +
|
||||||
|
"gfcGCCsGAQUFBwICMIHqMCcWIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9y\n" +
|
||||||
|
"aXR5MAMCAQEagb5UaGlzIGNlcnRpZmljYXRlIHdhcyBpc3N1ZWQgYWNjb3JkaW5n\n" +
|
||||||
|
"IHRvIHRoZSBDbGFzcyAyIFZhbGlkYXRpb24gcmVxdWlyZW1lbnRzIG9mIHRoZSBT\n" +
|
||||||
|
"dGFydENvbSBDQSBwb2xpY3ksIHJlbGlhbmNlIG9ubHkgZm9yIHRoZSBpbnRlbmRl\n" +
|
||||||
|
"ZCBwdXJwb3NlIGluIGNvbXBsaWFuY2Ugb2YgdGhlIHJlbHlpbmcgcGFydHkgb2Js\n" +
|
||||||
|
"aWdhdGlvbnMuMDUGA1UdHwQuMCwwKqAooCaGJGh0dHA6Ly9jcmwuc3RhcnRzc2wu\n" +
|
||||||
|
"Y29tL2NydDItY3JsLmNybDCBjgYIKwYBBQUHAQEEgYEwfzA5BggrBgEFBQcwAYYt\n" +
|
||||||
|
"aHR0cDovL29jc3Auc3RhcnRzc2wuY29tL3N1Yi9jbGFzczIvc2VydmVyL2NhMEIG\n" +
|
||||||
|
"CCsGAQUFBzAChjZodHRwOi8vYWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9zdWIuY2xh\n" +
|
||||||
|
"c3MyLnNlcnZlci5jYS5jcnQwIwYDVR0SBBwwGoYYaHR0cDovL3d3dy5zdGFydHNz\n" +
|
||||||
|
"bC5jb20vMA0GCSqGSIb3DQEBCwUAA4IBAQBVkvlwVLfnTNZh1c8j+PQ1t2n6x1dh\n" +
|
||||||
|
"tQtZiAYWKvZwi+XqLwU8q2zMxKrTDuqyEVyfCtWCiC1Vkpz72pcyXz2dKu7F7ZVL\n" +
|
||||||
|
"86uVHcc1jAGmL59UCXz8LFbfAMcoVQW1f2WtNwsa/WGnPUes3jFSec+shB+XCpvE\n" +
|
||||||
|
"WU6mfcZD5TyvbC79Kn5e3Iq+B4DaXhU/BXASRbORgYd8C+dqj++w0PAcMrmjn3D6\n" +
|
||||||
|
"EmL1ofqpQ8wCJd5C1b5Fr4RbbYpK8v8AASRcp2Qj9WJjyV882FvXOOFj5V2Jjcnh\n" +
|
||||||
|
"G0h67ElS/klu9rPaZ+vr3iIB56wvk08O2Wq1IND3sN+Ke3UsvuPqDxAv\n" +
|
||||||
|
"-----END CERTIFICATE-----\n";
|
||||||
|
|
||||||
|
private File mKeyStoreFile;
|
||||||
|
private LocalKeyStore mKeyStore;
|
||||||
|
private X509Certificate mCert1;
|
||||||
|
private X509Certificate mCert2;
|
||||||
|
private X509Certificate mCaCert;
|
||||||
|
private X509Certificate mCert3;
|
||||||
|
private X509Certificate mLinuxComFirstParentCert;
|
||||||
|
private X509Certificate mLinuxComCert;
|
||||||
|
|
||||||
|
|
||||||
|
public TrustManagerFactoryTest() throws CertificateException {
|
||||||
|
mCert1 = loadCert(K9_EXAMPLE_COM_CERT1);
|
||||||
|
mCert2 = loadCert(K9_EXAMPLE_COM_CERT2);
|
||||||
|
mCaCert = loadCert(CA_CERT);
|
||||||
|
mCert3 = loadCert(CERT3);
|
||||||
|
mLinuxComFirstParentCert = loadCert(LINUX_COM_FIRST_PARENT_CERT);
|
||||||
|
mLinuxComCert = loadCert(LINUX_COM_CERT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private X509Certificate loadCert(String encodedCert) throws CertificateException {
|
||||||
|
CertificateFactory certFactory = CertificateFactory.getInstance("X509");
|
||||||
|
return (X509Certificate) certFactory.generateCertificate(
|
||||||
|
new ByteArrayInputStream(encodedCert.getBytes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
mKeyStoreFile = File.createTempFile("localKeyStore", null,
|
||||||
|
InstrumentationRegistry.getTargetContext().getCacheDir());
|
||||||
|
mKeyStore = LocalKeyStore.getInstance();
|
||||||
|
mKeyStore.setKeyStoreFile(mKeyStoreFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
mKeyStoreFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if TrustManagerFactory supports a host with different certificates for different
|
||||||
|
* services (e.g. SMTP and IMAP).
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This test is to make sure entries in the keystore file aren't overwritten.
|
||||||
|
* See <a href="https://code.google.com/p/k9mail/issues/detail?id=1326">Issue 1326</a>.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
* if anything goes wrong
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDifferentCertificatesOnSameServer() throws Exception {
|
||||||
|
mKeyStore.addCertificate(NOT_MATCHING_HOST, PORT1, mCert1);
|
||||||
|
mKeyStore.addCertificate(NOT_MATCHING_HOST, PORT2, mCert2);
|
||||||
|
|
||||||
|
X509TrustManager trustManager1 = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1);
|
||||||
|
X509TrustManager trustManager2 = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT2);
|
||||||
|
trustManager2.checkServerTrusted(new X509Certificate[] { mCert2 }, "authType");
|
||||||
|
trustManager1.checkServerTrusted(new X509Certificate[] { mCert1 }, "authType");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSelfSignedCertificateMatchingHost() throws Exception {
|
||||||
|
mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert1);
|
||||||
|
X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1);
|
||||||
|
trustManager.checkServerTrusted(new X509Certificate[] { mCert1 }, "authType");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSelfSignedCertificateNotMatchingHost() throws Exception {
|
||||||
|
mKeyStore.addCertificate(NOT_MATCHING_HOST, PORT1, mCert1);
|
||||||
|
X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1);
|
||||||
|
trustManager.checkServerTrusted(new X509Certificate[] { mCert1 }, "authType");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWrongCertificate() throws Exception {
|
||||||
|
mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert1);
|
||||||
|
X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1);
|
||||||
|
assertCertificateRejection(trustManager, new X509Certificate[] { mCert2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCertificateOfOtherHost() throws Exception {
|
||||||
|
mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert1);
|
||||||
|
mKeyStore.addCertificate(MATCHING_HOST, PORT2, mCert2);
|
||||||
|
|
||||||
|
X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1);
|
||||||
|
assertCertificateRejection(trustManager, new X509Certificate[] { mCert2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUntrustedCertificateChain() throws Exception {
|
||||||
|
X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1);
|
||||||
|
assertCertificateRejection(trustManager, new X509Certificate[] { mCert3, mCaCert });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLocallyTrustedCertificateChain() throws Exception {
|
||||||
|
mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert3);
|
||||||
|
|
||||||
|
X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1);
|
||||||
|
trustManager.checkServerTrusted(new X509Certificate[] { mCert3, mCaCert }, "authType");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLocallyTrustedCertificateChainNotMatchingHost() throws Exception {
|
||||||
|
mKeyStore.addCertificate(NOT_MATCHING_HOST, PORT1, mCert3);
|
||||||
|
|
||||||
|
X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1);
|
||||||
|
trustManager.checkServerTrusted(new X509Certificate[] { mCert3, mCaCert }, "authType");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGloballyTrustedCertificateChain() throws Exception {
|
||||||
|
X509TrustManager trustManager = TrustManagerFactory.get("www.linux.com", PORT1);
|
||||||
|
X509Certificate[] certificates = new X509Certificate[] { mLinuxComCert, mLinuxComFirstParentCert};
|
||||||
|
trustManager.checkServerTrusted(certificates, "authType");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGloballyTrustedCertificateNotMatchingHost() throws Exception {
|
||||||
|
X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1);
|
||||||
|
assertCertificateRejection(trustManager, new X509Certificate[] { mLinuxComCert, mLinuxComFirstParentCert});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGloballyTrustedCertificateNotMatchingHostOverride() throws Exception {
|
||||||
|
mKeyStore.addCertificate(MATCHING_HOST, PORT1, mLinuxComCert);
|
||||||
|
|
||||||
|
X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1);
|
||||||
|
X509Certificate[] certificates = new X509Certificate[] { mLinuxComCert, mLinuxComFirstParentCert};
|
||||||
|
trustManager.checkServerTrusted(certificates, "authType");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertCertificateRejection(X509TrustManager trustManager,
|
||||||
|
X509Certificate[] certificates) {
|
||||||
|
boolean certificateValid;
|
||||||
|
try {
|
||||||
|
trustManager.checkServerTrusted(certificates, "authType");
|
||||||
|
certificateValid = true;
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
certificateValid = false;
|
||||||
|
}
|
||||||
|
assertFalse("The certificate should have been rejected but wasn't", certificateValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKeyStoreLoading() throws Exception {
|
||||||
|
mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert1);
|
||||||
|
mKeyStore.addCertificate(NOT_MATCHING_HOST, PORT2, mCert2);
|
||||||
|
assertTrue(mKeyStore.isValidCertificate(mCert1, MATCHING_HOST, PORT1));
|
||||||
|
assertTrue(mKeyStore.isValidCertificate(mCert2, NOT_MATCHING_HOST, PORT2));
|
||||||
|
|
||||||
|
// reload store from same file
|
||||||
|
mKeyStore.setKeyStoreFile(mKeyStoreFile);
|
||||||
|
assertTrue(mKeyStore.isValidCertificate(mCert1, MATCHING_HOST, PORT1));
|
||||||
|
assertTrue(mKeyStore.isValidCertificate(mCert2, NOT_MATCHING_HOST, PORT2));
|
||||||
|
|
||||||
|
// reload store from empty file
|
||||||
|
mKeyStoreFile.delete();
|
||||||
|
mKeyStore.setKeyStoreFile(mKeyStoreFile);
|
||||||
|
assertFalse(mKeyStore.isValidCertificate(mCert1, MATCHING_HOST, PORT1));
|
||||||
|
assertFalse(mKeyStore.isValidCertificate(mCert2, NOT_MATCHING_HOST, PORT2));
|
||||||
|
}
|
||||||
|
}
|
2
k9mail-library/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="com.fsck.k9.mail" />
|
@ -3,6 +3,7 @@ package com.fsck.k9.mail;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.apache.james.mime4j.MimeException;
|
import org.apache.james.mime4j.MimeException;
|
||||||
import org.apache.james.mime4j.codec.EncoderUtil;
|
import org.apache.james.mime4j.codec.EncoderUtil;
|
||||||
@ -10,42 +11,24 @@ import org.apache.james.mime4j.dom.address.Mailbox;
|
|||||||
import org.apache.james.mime4j.dom.address.MailboxList;
|
import org.apache.james.mime4j.dom.address.MailboxList;
|
||||||
import org.apache.james.mime4j.field.address.AddressBuilder;
|
import org.apache.james.mime4j.field.address.AddressBuilder;
|
||||||
|
|
||||||
import android.text.Spannable;
|
import android.text.TextUtils;
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.SpannableStringBuilder;
|
|
||||||
import android.text.style.ForegroundColorSpan;
|
|
||||||
import android.text.util.Rfc822Token;
|
import android.text.util.Rfc822Token;
|
||||||
import android.text.util.Rfc822Tokenizer;
|
import android.text.util.Rfc822Tokenizer;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.fsck.k9.K9;
|
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
|
||||||
import com.fsck.k9.helper.Contacts;
|
|
||||||
import com.fsck.k9.helper.StringUtils;
|
|
||||||
import com.fsck.k9.helper.Utility;
|
|
||||||
|
|
||||||
|
|
||||||
public class Address {
|
public class Address {
|
||||||
/**
|
private static final Pattern ATOM = Pattern.compile("^(?:[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]|\\s)+$");
|
||||||
* If the number of addresses exceeds this value the addresses aren't
|
|
||||||
* resolved to the names of Android contacts.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* TODO: This number was chosen arbitrarily and should be determined by
|
|
||||||
* performance tests.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @see Address#toFriendly(Address[], Contacts)
|
|
||||||
*/
|
|
||||||
private static final int TOO_MANY_ADDRESSES = 50;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immutable empty {@link Address} array
|
* Immutable empty {@link Address} array
|
||||||
*/
|
*/
|
||||||
private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0];
|
private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0];
|
||||||
|
|
||||||
String mAddress;
|
private String mAddress;
|
||||||
|
|
||||||
String mPersonal;
|
private String mPersonal;
|
||||||
|
|
||||||
|
|
||||||
public Address(Address address) {
|
public Address(Address address) {
|
||||||
@ -68,7 +51,7 @@ public class Address {
|
|||||||
Rfc822Token token = tokens[0];
|
Rfc822Token token = tokens[0];
|
||||||
mAddress = token.getAddress();
|
mAddress = token.getAddress();
|
||||||
String name = token.getName();
|
String name = token.getName();
|
||||||
if (!StringUtils.isNullOrEmpty(name)) {
|
if (!TextUtils.isEmpty(name)) {
|
||||||
/*
|
/*
|
||||||
* Don't use the "personal" argument if "address" is of the form:
|
* Don't use the "personal" argument if "address" is of the form:
|
||||||
* James Bond <james.bond@mi6.uk>
|
* James Bond <james.bond@mi6.uk>
|
||||||
@ -92,6 +75,16 @@ public class Address {
|
|||||||
return mAddress;
|
return mAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getHostname() {
|
||||||
|
int hostIdx = mAddress.lastIndexOf("@");
|
||||||
|
|
||||||
|
if (hostIdx == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mAddress.substring(hostIdx+1);
|
||||||
|
}
|
||||||
|
|
||||||
public void setAddress(String address) {
|
public void setAddress(String address) {
|
||||||
this.mAddress = address;
|
this.mAddress = address;
|
||||||
}
|
}
|
||||||
@ -119,11 +112,11 @@ public class Address {
|
|||||||
*/
|
*/
|
||||||
public static Address[] parseUnencoded(String addressList) {
|
public static Address[] parseUnencoded(String addressList) {
|
||||||
List<Address> addresses = new ArrayList<Address>();
|
List<Address> addresses = new ArrayList<Address>();
|
||||||
if (!StringUtils.isNullOrEmpty(addressList)) {
|
if (!TextUtils.isEmpty(addressList)) {
|
||||||
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
|
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
|
||||||
for (Rfc822Token token : tokens) {
|
for (Rfc822Token token : tokens) {
|
||||||
String address = token.getAddress();
|
String address = token.getAddress();
|
||||||
if (!StringUtils.isNullOrEmpty(address)) {
|
if (!TextUtils.isEmpty(address)) {
|
||||||
addresses.add(new Address(token.getAddress(), token.getName(), false));
|
addresses.add(new Address(token.getAddress(), token.getName(), false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,7 +132,7 @@ public class Address {
|
|||||||
* @return An array of 0 or more Addresses.
|
* @return An array of 0 or more Addresses.
|
||||||
*/
|
*/
|
||||||
public static Address[] parse(String addressList) {
|
public static Address[] parse(String addressList) {
|
||||||
if (StringUtils.isNullOrEmpty(addressList)) {
|
if (TextUtils.isEmpty(addressList)) {
|
||||||
return EMPTY_ADDRESS_ARRAY;
|
return EMPTY_ADDRESS_ARRAY;
|
||||||
}
|
}
|
||||||
List<Address> addresses = new ArrayList<Address>();
|
List<Address> addresses = new ArrayList<Address>();
|
||||||
@ -152,12 +145,12 @@ public class Address {
|
|||||||
Mailbox mailbox = (Mailbox)address;
|
Mailbox mailbox = (Mailbox)address;
|
||||||
addresses.add(new Address(mailbox.getLocalPart() + "@" + mailbox.getDomain(), mailbox.getName(), false));
|
addresses.add(new Address(mailbox.getLocalPart() + "@" + mailbox.getDomain(), mailbox.getName(), false));
|
||||||
} else {
|
} else {
|
||||||
Log.e(K9.LOG_TAG, "Unknown address type from Mime4J: "
|
Log.e(LOG_TAG, "Unknown address type from Mime4J: "
|
||||||
+ address.getClass().toString());
|
+ address.getClass().toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (MimeException pe) {
|
} catch (MimeException pe) {
|
||||||
Log.e(K9.LOG_TAG, "MimeException in Address.parse()", pe);
|
Log.e(LOG_TAG, "MimeException in Address.parse()", pe);
|
||||||
//but we do an silent failover : we just use the given string as name with empty address
|
//but we do an silent failover : we just use the given string as name with empty address
|
||||||
addresses.add(new Address(null, addressList, false));
|
addresses.add(new Address(null, addressList, false));
|
||||||
}
|
}
|
||||||
@ -167,20 +160,28 @@ public class Address {
|
|||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (o instanceof Address) {
|
if (o instanceof Address) {
|
||||||
return getAddress().equals(((Address) o).getAddress());
|
Address other = (Address) o;
|
||||||
|
if (mPersonal != null && other.mPersonal != null && !mPersonal.equals(other.mPersonal)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mAddress.equals(other.mAddress);
|
||||||
}
|
}
|
||||||
return super.equals(o);
|
return super.equals(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return getAddress().hashCode();
|
int hash = mAddress.hashCode();
|
||||||
|
if (mPersonal != null) {
|
||||||
|
hash += 3 * mPersonal.hashCode();
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
if (!StringUtils.isNullOrEmpty(mPersonal)) {
|
if (!TextUtils.isEmpty(mPersonal)) {
|
||||||
return Utility.quoteAtoms(mPersonal) + " <" + mAddress + ">";
|
return quoteAtoms(mPersonal) + " <" + mAddress + ">";
|
||||||
} else {
|
} else {
|
||||||
return mAddress;
|
return mAddress;
|
||||||
}
|
}
|
||||||
@ -190,18 +191,11 @@ public class Address {
|
|||||||
if (addresses == null) {
|
if (addresses == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
StringBuilder sb = new StringBuilder();
|
return TextUtils.join(", ", addresses);
|
||||||
for (int i = 0; i < addresses.length; i++) {
|
|
||||||
sb.append(addresses[i].toString());
|
|
||||||
if (i < addresses.length - 1) {
|
|
||||||
sb.append(", ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toEncodedString() {
|
public String toEncodedString() {
|
||||||
if (!StringUtils.isNullOrEmpty(mPersonal)) {
|
if (!TextUtils.isEmpty(mPersonal)) {
|
||||||
return EncoderUtil.encodeAddressDisplayName(mPersonal) + " <" + mAddress + ">";
|
return EncoderUtil.encodeAddressDisplayName(mPersonal) + " <" + mAddress + ">";
|
||||||
} else {
|
} else {
|
||||||
return mAddress;
|
return mAddress;
|
||||||
@ -222,77 +216,6 @@ public class Address {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns either the personal portion of the Address or the address portion if the personal
|
|
||||||
* is not available.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public CharSequence toFriendly() {
|
|
||||||
return toFriendly((Contacts)null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the contact this email address belongs to if
|
|
||||||
* the {@link Contacts contacts} parameter is not {@code null} and a
|
|
||||||
* contact is found. Otherwise the personal portion of the {@link Address}
|
|
||||||
* is returned. If that isn't available either, the email address is
|
|
||||||
* returned.
|
|
||||||
*
|
|
||||||
* @param contacts
|
|
||||||
* A {@link Contacts} instance or {@code null}.
|
|
||||||
* @return
|
|
||||||
* A "friendly" name for this {@link Address}.
|
|
||||||
*/
|
|
||||||
public CharSequence toFriendly(final Contacts contacts) {
|
|
||||||
if (!K9.showCorrespondentNames()) {
|
|
||||||
return mAddress;
|
|
||||||
|
|
||||||
} else if (contacts != null) {
|
|
||||||
final String name = contacts.getNameForAddress(mAddress);
|
|
||||||
|
|
||||||
// TODO: The results should probably be cached for performance reasons.
|
|
||||||
|
|
||||||
if (name != null) {
|
|
||||||
if (K9.changeContactNameColor()) {
|
|
||||||
final SpannableString coloredName = new SpannableString(name);
|
|
||||||
coloredName.setSpan(new ForegroundColorSpan(K9.getContactNameColor()),
|
|
||||||
0,
|
|
||||||
coloredName.length(),
|
|
||||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
||||||
);
|
|
||||||
return coloredName;
|
|
||||||
} else {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (!StringUtils.isNullOrEmpty(mPersonal)) ? mPersonal : mAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CharSequence toFriendly(Address[] addresses) {
|
|
||||||
return toFriendly(addresses, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CharSequence toFriendly(Address[] addresses, Contacts contacts) {
|
|
||||||
if (addresses == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addresses.length >= TOO_MANY_ADDRESSES) {
|
|
||||||
// Don't look up contacts if the number of addresses is very high.
|
|
||||||
contacts = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
|
||||||
for (int i = 0; i < addresses.length; i++) {
|
|
||||||
sb.append(addresses[i].toFriendly(contacts));
|
|
||||||
if (i < addresses.length - 1) {
|
|
||||||
sb.append(',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unpacks an address list previously packed with packAddressList()
|
* Unpacks an address list previously packed with packAddressList()
|
||||||
@ -303,7 +226,7 @@ public class Address {
|
|||||||
if (addressList == null) {
|
if (addressList == null) {
|
||||||
return new Address[] { };
|
return new Address[] { };
|
||||||
}
|
}
|
||||||
ArrayList<Address> addresses = new ArrayList<Address>();
|
List<Address> addresses = new ArrayList<Address>();
|
||||||
int length = addressList.length();
|
int length = addressList.length();
|
||||||
int pairStartIndex = 0;
|
int pairStartIndex = 0;
|
||||||
int pairEndIndex = 0;
|
int pairEndIndex = 0;
|
||||||
@ -357,4 +280,44 @@ public class Address {
|
|||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quote a string, if necessary, based upon the definition of an "atom," as defined by RFC2822
|
||||||
|
* (http://tools.ietf.org/html/rfc2822#section-3.2.4). Strings that consist purely of atoms are
|
||||||
|
* left unquoted; anything else is returned as a quoted string.
|
||||||
|
* @param text String to quote.
|
||||||
|
* @return Possibly quoted string.
|
||||||
|
*/
|
||||||
|
public static String quoteAtoms(final String text) {
|
||||||
|
if (ATOM.matcher(text).matches()) {
|
||||||
|
return text;
|
||||||
|
} else {
|
||||||
|
return quoteString(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the given string starts and ends with the double quote character. The string is not modified in any way except to add the
|
||||||
|
* double quote character to start and end if it's not already there.
|
||||||
|
* sample -> "sample"
|
||||||
|
* "sample" -> "sample"
|
||||||
|
* ""sample"" -> "sample"
|
||||||
|
* "sample"" -> "sample"
|
||||||
|
* sa"mp"le -> "sa"mp"le"
|
||||||
|
* "sa"mp"le" -> "sa"mp"le"
|
||||||
|
* (empty string) -> ""
|
||||||
|
* " -> ""
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String quoteString(String s) {
|
||||||
|
if (s == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!s.matches("^\".*\"$")) {
|
||||||
|
return "\"" + s + "\"";
|
||||||
|
} else {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
27
k9mail-library/src/main/java/com/fsck/k9/mail/AuthType.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
public enum AuthType {
|
||||||
|
/*
|
||||||
|
* The names of these authentication types are saved as strings when
|
||||||
|
* settings are exported and are also saved as part of the Server URI stored
|
||||||
|
* in the account settings.
|
||||||
|
*
|
||||||
|
* PLAIN and CRAM_MD5 originally referred to specific SASL authentication
|
||||||
|
* mechanisms. Their meaning has since been broadened to mean authentication
|
||||||
|
* with unencrypted and encrypted passwords, respectively. Nonetheless,
|
||||||
|
* their original names have been retained for backward compatibility with
|
||||||
|
* user settings.
|
||||||
|
*/
|
||||||
|
PLAIN,
|
||||||
|
CRAM_MD5,
|
||||||
|
EXTERNAL,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following are obsolete authentication settings that were used with
|
||||||
|
* SMTP. They are no longer presented to the user as options, but they may
|
||||||
|
* still exist in a user's settings from a previous version or may be found
|
||||||
|
* when importing settings.
|
||||||
|
*/
|
||||||
|
AUTOMATIC,
|
||||||
|
LOGIN
|
||||||
|
}
|
@ -17,21 +17,21 @@ public class Authentication {
|
|||||||
* @param b64Nonce The nonce as base64-encoded string.
|
* @param b64Nonce The nonce as base64-encoded string.
|
||||||
* @return The CRAM-MD5 response.
|
* @return The CRAM-MD5 response.
|
||||||
*
|
*
|
||||||
* @throws AuthenticationFailedException If something went wrong.
|
* @throws MessagingException If something went wrong.
|
||||||
*
|
*
|
||||||
* @see Authentication#computeCramMd5Bytes(String, String, byte[])
|
* @see Authentication#computeCramMd5Bytes(String, String, byte[])
|
||||||
*/
|
*/
|
||||||
public static String computeCramMd5(String username, String password, String b64Nonce)
|
public static String computeCramMd5(String username, String password, String b64Nonce)
|
||||||
throws AuthenticationFailedException {
|
throws MessagingException {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] b64NonceBytes = b64Nonce.getBytes(US_ASCII);
|
byte[] b64NonceBytes = b64Nonce.getBytes(US_ASCII);
|
||||||
byte[] b64CRAM = computeCramMd5Bytes(username, password, b64NonceBytes);
|
byte[] b64CRAM = computeCramMd5Bytes(username, password, b64NonceBytes);
|
||||||
return new String(b64CRAM, US_ASCII);
|
return new String(b64CRAM, US_ASCII);
|
||||||
} catch (AuthenticationFailedException e) {
|
} catch (MessagingException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new AuthenticationFailedException("This shouldn't happen", e);
|
throw new MessagingException("This shouldn't happen", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,17 +44,17 @@ public class Authentication {
|
|||||||
* @param b64Nonce The nonce as base64-encoded byte array.
|
* @param b64Nonce The nonce as base64-encoded byte array.
|
||||||
* @return The CRAM-MD5 response as byte array.
|
* @return The CRAM-MD5 response as byte array.
|
||||||
*
|
*
|
||||||
* @throws AuthenticationFailedException If something went wrong.
|
* @throws MessagingException If something went wrong.
|
||||||
*
|
*
|
||||||
* @see <a href="https://tools.ietf.org/html/rfc2195">RFC 2195</a>
|
* @see <a href="https://tools.ietf.org/html/rfc2195">RFC 2195</a>
|
||||||
*/
|
*/
|
||||||
public static byte[] computeCramMd5Bytes(String username, String password, byte[] b64Nonce)
|
public static byte[] computeCramMd5Bytes(String username, String password, byte[] b64Nonce)
|
||||||
throws AuthenticationFailedException {
|
throws MessagingException {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] nonce = Base64.decodeBase64(b64Nonce);
|
byte[] nonce = Base64.decodeBase64(b64Nonce);
|
||||||
|
|
||||||
byte[] secretBytes = password.getBytes(US_ASCII);
|
byte[] secretBytes = password.getBytes();
|
||||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
if (secretBytes.length > 64) {
|
if (secretBytes.length > 64) {
|
||||||
secretBytes = md.digest(secretBytes);
|
secretBytes = md.digest(secretBytes);
|
||||||
@ -74,12 +74,12 @@ public class Authentication {
|
|||||||
byte[] result = md.digest(firstPass);
|
byte[] result = md.digest(firstPass);
|
||||||
|
|
||||||
String plainCRAM = username + " " + new String(Hex.encodeHex(result));
|
String plainCRAM = username + " " + new String(Hex.encodeHex(result));
|
||||||
byte[] b64CRAM = Base64.encodeBase64(plainCRAM.getBytes(US_ASCII));
|
byte[] b64CRAM = Base64.encodeBase64(plainCRAM.getBytes());
|
||||||
|
|
||||||
return b64CRAM;
|
return b64CRAM;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new AuthenticationFailedException("Something went wrong during CRAM-MD5 computation", e);
|
throw new MessagingException("Something went wrong during CRAM-MD5 computation", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
26
k9mail-library/src/main/java/com/fsck/k9/mail/Body.java
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public interface Body {
|
||||||
|
/**
|
||||||
|
* Returns the raw data of the body, without transfer encoding etc applied.
|
||||||
|
* TODO perhaps it would be better to have an intermediate "simple part" class where this method could reside
|
||||||
|
* because it makes no sense for multiparts
|
||||||
|
*/
|
||||||
|
public InputStream getInputStream() throws MessagingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the content transfer encoding (7bit, 8bit, quoted-printable or base64).
|
||||||
|
*/
|
||||||
|
public void setEncoding(String encoding) throws MessagingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the body's data to the given {@link OutputStream}.
|
||||||
|
* The written data is transfer encoded (e.g. transformed to Base64 when needed).
|
||||||
|
*/
|
||||||
|
public void writeTo(OutputStream out) throws IOException, MessagingException;
|
||||||
|
}
|
27
k9mail-library/src/main/java/com/fsck/k9/mail/BodyPart.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class BodyPart implements Part {
|
||||||
|
private String serverExtra;
|
||||||
|
private Multipart parent;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServerExtra() {
|
||||||
|
return serverExtra;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setServerExtra(String serverExtra) {
|
||||||
|
this.serverExtra = serverExtra;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Multipart getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParent(Multipart parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void setEncoding(String encoding) throws MessagingException;
|
||||||
|
}
|
@ -13,14 +13,8 @@ public class CertificateChainException extends CertificateException {
|
|||||||
private static final long serialVersionUID = 1103894512106650107L;
|
private static final long serialVersionUID = 1103894512106650107L;
|
||||||
private X509Certificate[] mCertChain;
|
private X509Certificate[] mCertChain;
|
||||||
|
|
||||||
public CertificateChainException(String msg, X509Certificate[] chain) {
|
public CertificateChainException(String msg, X509Certificate[] chain, Throwable cause) {
|
||||||
super(msg);
|
super(msg, cause);
|
||||||
setCertChain(chain);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CertificateChainException(CertificateException ce,
|
|
||||||
X509Certificate[] chain) {
|
|
||||||
super.initCause(ce);
|
|
||||||
setCertChain(chain);
|
setCertChain(chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,5 +24,4 @@ public class CertificateChainException extends CertificateException {
|
|||||||
public X509Certificate[] getCertChain() {
|
public X509Certificate[] getCertChain() {
|
||||||
return mCertChain;
|
return mCertChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
|
||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
import java.security.cert.CertPathValidatorException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
|
|
||||||
|
import android.security.KeyChainException;
|
||||||
|
|
||||||
|
public class CertificateValidationException extends MessagingException {
|
||||||
|
public static final long serialVersionUID = -1;
|
||||||
|
private final Reason mReason;
|
||||||
|
private X509Certificate[] mCertChain;
|
||||||
|
private boolean mNeedsUserAttention = false;
|
||||||
|
private String mAlias;
|
||||||
|
|
||||||
|
public enum Reason {
|
||||||
|
Unknown, UseMessage, Expired, MissingCapability, RetrievalFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
public CertificateValidationException(String message) {
|
||||||
|
this(message, Reason.UseMessage, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CertificateValidationException(Reason reason) {
|
||||||
|
this(null, reason, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CertificateValidationException(String message, Reason reason, String alias) {
|
||||||
|
super(message);
|
||||||
|
/*
|
||||||
|
* Instances created without a Throwable parameter as a cause are
|
||||||
|
* presumed to need user attention.
|
||||||
|
*/
|
||||||
|
mNeedsUserAttention = true;
|
||||||
|
mReason = reason;
|
||||||
|
mAlias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CertificateValidationException(final String message, Throwable throwable) {
|
||||||
|
super(message, throwable);
|
||||||
|
mReason = Reason.Unknown;
|
||||||
|
scanForCause();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlias() {
|
||||||
|
return mAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Reason getReason() {
|
||||||
|
return mReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scanForCause() {
|
||||||
|
Throwable throwable = getCause();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* User attention is required if the server certificate was deemed
|
||||||
|
* invalid or if there was a problem with a client certificate.
|
||||||
|
*
|
||||||
|
* A CertificateException is known to be thrown by the default
|
||||||
|
* X509TrustManager.checkServerTrusted() if the server certificate
|
||||||
|
* doesn't validate. The cause of the CertificateException will be a
|
||||||
|
* CertPathValidatorException. However, it's unlikely those exceptions
|
||||||
|
* will be encountered here, because they are caught in
|
||||||
|
* SecureX509TrustManager.checkServerTrusted(), which throws a
|
||||||
|
* CertificateChainException instead (an extension of
|
||||||
|
* CertificateException).
|
||||||
|
*
|
||||||
|
* A CertificateChainException will likely result in (or, be the cause
|
||||||
|
* of) an SSLHandshakeException (an extension of SSLException).
|
||||||
|
*
|
||||||
|
* The various mail protocol handlers (IMAP, POP3, ...) will catch an
|
||||||
|
* SSLException and throw a CertificateValidationException (this class)
|
||||||
|
* with the SSLException as the cause. (They may also throw a
|
||||||
|
* CertificateValidationException when STARTTLS is not available, just
|
||||||
|
* for the purpose of triggering a user notification.)
|
||||||
|
*
|
||||||
|
* SSLHandshakeException is also known to occur if the *client*
|
||||||
|
* certificate was not accepted by the server (unknown CA, certificate
|
||||||
|
* expired, etc.). In this case, the SSLHandshakeException will not have
|
||||||
|
* a CertificateChainException as a cause.
|
||||||
|
*
|
||||||
|
* KeyChainException is known to occur if the device has no client
|
||||||
|
* certificate that's associated with the alias stored in the server
|
||||||
|
* settings.
|
||||||
|
*/
|
||||||
|
while (throwable != null
|
||||||
|
&& !(throwable instanceof CertPathValidatorException)
|
||||||
|
&& !(throwable instanceof CertificateException)
|
||||||
|
&& !(throwable instanceof KeyChainException)
|
||||||
|
&& !(throwable instanceof SSLHandshakeException)) {
|
||||||
|
throwable = throwable.getCause();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (throwable != null) {
|
||||||
|
mNeedsUserAttention = true;
|
||||||
|
|
||||||
|
// See if there is a server certificate chain attached to the SSLHandshakeException
|
||||||
|
if (throwable instanceof SSLHandshakeException) {
|
||||||
|
while (throwable != null && !(throwable instanceof CertificateChainException)) {
|
||||||
|
throwable = throwable.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (throwable != null && throwable instanceof CertificateChainException) {
|
||||||
|
mCertChain = ((CertificateChainException) throwable).getCertChain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean needsUserAttention() {
|
||||||
|
return mNeedsUserAttention;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the cause of this {@link CertificateValidationException} was a
|
||||||
|
* {@link CertificateChainException}, then the offending chain is available
|
||||||
|
* for return.
|
||||||
|
*
|
||||||
|
* @return An {@link X509Certificate X509Certificate[]} containing the Cert.
|
||||||
|
* chain, or else null.
|
||||||
|
*/
|
||||||
|
public X509Certificate[] getCertChain() {
|
||||||
|
return mCertChain;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A CompositeBody is a {@link Body} extension that can contain subparts that
|
||||||
|
* may require recursing through or iterating over when converting the
|
||||||
|
* CompositeBody from 8bit to 7bit encoding. The {@link Part} to which a
|
||||||
|
* CompositeBody belongs is only permitted to use 8bit or 7bit content transfer
|
||||||
|
* encoding for the CompositeBody.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface CompositeBody extends Body {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called just prior to transmission, once the type of transport is known to
|
||||||
|
* be 7bit.
|
||||||
|
* <p>
|
||||||
|
* All subparts that are 8bit and of type {@link CompositeBody} will be
|
||||||
|
* converted to 7bit and recursed. All supbparts that are 8bit but not
|
||||||
|
* of type CompositeBody will be converted to quoted-printable. Bodies with
|
||||||
|
* encodings other than 8bit remain unchanged.
|
||||||
|
*
|
||||||
|
* @throws MessagingException
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public abstract void setUsing7bitTransport() throws MessagingException;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
public enum ConnectionSecurity {
|
||||||
|
NONE,
|
||||||
|
STARTTLS_REQUIRED,
|
||||||
|
SSL_TLS_REQUIRED
|
||||||
|
}
|
@ -3,17 +3,13 @@ package com.fsck.k9.mail;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.K9;
|
|
||||||
import com.fsck.k9.Preferences;
|
|
||||||
import com.fsck.k9.controller.MessageRetrievalListener;
|
|
||||||
|
|
||||||
|
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
|
||||||
|
|
||||||
public abstract class Folder {
|
public abstract class Folder<T extends Message> {
|
||||||
protected final Account mAccount;
|
|
||||||
|
|
||||||
private String status = null;
|
private String status = null;
|
||||||
private long lastChecked = 0;
|
private long lastChecked = 0;
|
||||||
private long lastPush = 0;
|
private long lastPush = 0;
|
||||||
@ -30,10 +26,6 @@ public abstract class Folder {
|
|||||||
HOLDS_FOLDERS, HOLDS_MESSAGES,
|
HOLDS_FOLDERS, HOLDS_MESSAGES,
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Folder(Account account) {
|
|
||||||
mAccount = account;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forces an open of the MailProvider. If the provider is already open this
|
* Forces an open of the MailProvider. If the provider is already open this
|
||||||
* function returns without doing anything.
|
* function returns without doing anything.
|
||||||
@ -81,7 +73,7 @@ public abstract class Folder {
|
|||||||
public abstract int getUnreadMessageCount() throws MessagingException;
|
public abstract int getUnreadMessageCount() throws MessagingException;
|
||||||
public abstract int getFlaggedMessageCount() throws MessagingException;
|
public abstract int getFlaggedMessageCount() throws MessagingException;
|
||||||
|
|
||||||
public abstract Message getMessage(String uid) throws MessagingException;
|
public abstract T getMessage(String uid) throws MessagingException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the shells of messages between a range of UIDs and after a given date.
|
* Fetch the shells of messages between a range of UIDs and after a given date.
|
||||||
@ -92,7 +84,7 @@ public abstract class Folder {
|
|||||||
* @return List of messages
|
* @return List of messages
|
||||||
* @throws MessagingException
|
* @throws MessagingException
|
||||||
*/
|
*/
|
||||||
public abstract Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) throws MessagingException;
|
public abstract List<T> getMessages(int start, int end, Date earliestDate, MessageRetrievalListener<T> listener) throws MessagingException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the given list of messages. The specified listener is notified as
|
* Fetches the given list of messages. The specified listener is notified as
|
||||||
@ -102,36 +94,36 @@ public abstract class Folder {
|
|||||||
* @param listener Listener to notify as we download messages.
|
* @param listener Listener to notify as we download messages.
|
||||||
* @return List of messages
|
* @return List of messages
|
||||||
*/
|
*/
|
||||||
public abstract Message[] getMessages(MessageRetrievalListener listener) throws MessagingException;
|
public abstract List<T> getMessages(MessageRetrievalListener<T> listener) throws MessagingException;
|
||||||
|
|
||||||
public Message[] getMessages(MessageRetrievalListener listener, boolean includeDeleted) throws MessagingException {
|
public List<T> getMessages(MessageRetrievalListener<T> listener, boolean includeDeleted) throws MessagingException {
|
||||||
return getMessages(listener);
|
return getMessages(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Message[] getMessages(String[] uids, MessageRetrievalListener listener)
|
public abstract List<T> getMessages(String[] uids, MessageRetrievalListener<T> listener)
|
||||||
throws MessagingException;
|
throws MessagingException;
|
||||||
|
|
||||||
public abstract Map<String, String> appendMessages(Message[] messages) throws MessagingException;
|
public abstract Map<String, String> appendMessages(List<? extends Message> messages) throws MessagingException;
|
||||||
|
|
||||||
public Map<String, String> copyMessages(Message[] msgs, Folder folder) throws MessagingException {
|
public Map<String, String> copyMessages(List<? extends Message> msgs, Folder folder) throws MessagingException {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> moveMessages(Message[] msgs, Folder folder) throws MessagingException {
|
public Map<String, String> moveMessages(List<? extends Message> msgs, Folder folder) throws MessagingException {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(Message[] msgs, String trashFolderName) throws MessagingException {
|
public void delete(List<? extends Message> msgs, String trashFolderName) throws MessagingException {
|
||||||
for (Message message : msgs) {
|
for (Message message : msgs) {
|
||||||
Message myMessage = getMessage(message.getUid());
|
Message myMessage = getMessage(message.getUid());
|
||||||
myMessage.delete(trashFolderName);
|
myMessage.delete(trashFolderName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void setFlags(Message[] messages, Flag[] flags, boolean value)
|
public abstract void setFlags(List<? extends Message> messages, Set<Flag> flags, boolean value)
|
||||||
throws MessagingException;
|
throws MessagingException;
|
||||||
|
|
||||||
public abstract void setFlags(Flag[] flags, boolean value) throws MessagingException;
|
public abstract void setFlags(Set<Flag> flags, boolean value) throws MessagingException;
|
||||||
|
|
||||||
public abstract String getUidFromMessageId(Message message) throws MessagingException;
|
public abstract String getUidFromMessageId(Message message) throws MessagingException;
|
||||||
|
|
||||||
@ -146,16 +138,16 @@ public abstract class Folder {
|
|||||||
* @param listener Listener to notify as we fetch messages.
|
* @param listener Listener to notify as we fetch messages.
|
||||||
* @throws MessagingException
|
* @throws MessagingException
|
||||||
*/
|
*/
|
||||||
public abstract void fetch(Message[] messages, FetchProfile fp,
|
public abstract void fetch(List<T> messages, FetchProfile fp,
|
||||||
MessageRetrievalListener listener) throws MessagingException;
|
MessageRetrievalListener<T> listener) throws MessagingException;
|
||||||
|
|
||||||
public void fetchPart(Message message, Part part,
|
public void fetchPart(Message message, Part part,
|
||||||
MessageRetrievalListener listener) throws MessagingException {
|
MessageRetrievalListener<Message> listener) throws MessagingException {
|
||||||
// This is causing trouble. Disabled for now. See issue 1733
|
// This is causing trouble. Disabled for now. See issue 1733
|
||||||
//throw new RuntimeException("fetchPart() not implemented.");
|
//throw new RuntimeException("fetchPart() not implemented.");
|
||||||
|
|
||||||
if (K9.DEBUG)
|
if (K9MailLib.isDebug())
|
||||||
Log.d(K9.LOG_TAG, "fetchPart() not implemented.");
|
Log.d(LOG_TAG, "fetchPart() not implemented.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void delete(boolean recurse) throws MessagingException;
|
public abstract void delete(boolean recurse) throws MessagingException;
|
||||||
@ -170,7 +162,6 @@ public abstract class Folder {
|
|||||||
protected boolean mCanCreateKeywords = false;
|
protected boolean mCanCreateKeywords = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param oldPushState
|
* @param oldPushState
|
||||||
* @param message
|
* @param message
|
||||||
* @return empty string to clear the pushState, null to leave the state as-is
|
* @return empty string to clear the pushState, null to leave the state as-is
|
||||||
@ -223,10 +214,6 @@ public abstract class Folder {
|
|||||||
return getSyncClass();
|
return getSyncClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refresh(Preferences preferences) throws MessagingException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInTopGroup() {
|
public boolean isInTopGroup() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -239,11 +226,7 @@ public abstract class Folder {
|
|||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Account getAccount() {
|
public List<T> search(String queryString, final Set<Flag> requiredFlags, final Set<Flag> forbiddenFlags)
|
||||||
return mAccount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Message> search(String queryString, final Flag[] requiredFlags, final Flag[] forbiddenFlags)
|
|
||||||
throws MessagingException {
|
throws MessagingException {
|
||||||
throw new MessagingException("K-9 does not support searches on this folder type");
|
throw new MessagingException("K-9 does not support searches on this folder type");
|
||||||
}
|
}
|
101
k9mail-library/src/main/java/com/fsck/k9/mail/K9MailLib.java
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
|
||||||
|
public class K9MailLib {
|
||||||
|
private static DebugStatus debugStatus = new DefaultDebugStatus();
|
||||||
|
|
||||||
|
private K9MailLib() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String LOG_TAG = "k9";
|
||||||
|
public static final int PUSH_WAKE_LOCK_TIMEOUT = 60000;
|
||||||
|
public static final String IDENTITY_HEADER = "X-K9mail-Identity";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should K-9 log the conversation it has over the wire with
|
||||||
|
* SMTP servers?
|
||||||
|
*/
|
||||||
|
public static boolean DEBUG_PROTOCOL_SMTP = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should K-9 log the conversation it has over the wire with
|
||||||
|
* IMAP servers?
|
||||||
|
*/
|
||||||
|
public static boolean DEBUG_PROTOCOL_IMAP = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should K-9 log the conversation it has over the wire with
|
||||||
|
* POP3 servers?
|
||||||
|
*/
|
||||||
|
public static boolean DEBUG_PROTOCOL_POP3 = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should K-9 log the conversation it has over the wire with
|
||||||
|
* WebDAV servers?
|
||||||
|
*/
|
||||||
|
public static boolean DEBUG_PROTOCOL_WEBDAV = true;
|
||||||
|
|
||||||
|
public static boolean isDebug() {
|
||||||
|
return debugStatus.enabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDebugSensitive() {
|
||||||
|
return debugStatus.debugSensitive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDebugSensitive(boolean b) {
|
||||||
|
if (debugStatus instanceof WritableDebugStatus) {
|
||||||
|
((WritableDebugStatus) debugStatus).setSensitive(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDebug(boolean b) {
|
||||||
|
if (debugStatus instanceof WritableDebugStatus) {
|
||||||
|
((WritableDebugStatus) debugStatus).setEnabled(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface DebugStatus {
|
||||||
|
boolean enabled();
|
||||||
|
|
||||||
|
boolean debugSensitive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDebugStatus(DebugStatus status) {
|
||||||
|
if (status == null) {
|
||||||
|
throw new IllegalArgumentException("status cannot be null");
|
||||||
|
}
|
||||||
|
debugStatus = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface WritableDebugStatus extends DebugStatus {
|
||||||
|
void setEnabled(boolean enabled);
|
||||||
|
|
||||||
|
void setSensitive(boolean sensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DefaultDebugStatus implements WritableDebugStatus {
|
||||||
|
private boolean enabled;
|
||||||
|
private boolean sensitive;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean enabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean debugSensitive() {
|
||||||
|
return sensitive;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSensitive(boolean sensitive) {
|
||||||
|
this.sensitive = sensitive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,23 +2,19 @@
|
|||||||
package com.fsck.k9.mail;
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.fsck.k9.K9;
|
|
||||||
import com.fsck.k9.activity.MessageReference;
|
|
||||||
import com.fsck.k9.mail.filter.CountingOutputStream;
|
import com.fsck.k9.mail.filter.CountingOutputStream;
|
||||||
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
|
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
|
||||||
import com.fsck.k9.mail.store.UnavailableStorageException;
|
|
||||||
|
|
||||||
|
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
|
||||||
|
|
||||||
public abstract class Message implements Part, Body {
|
public abstract class Message implements Part, CompositeBody {
|
||||||
private static final Flag[] EMPTY_FLAG_ARRAY = new Flag[0];
|
|
||||||
|
|
||||||
private MessageReference mReference = null;
|
|
||||||
|
|
||||||
public enum RecipientType {
|
public enum RecipientType {
|
||||||
TO, CC, BCC,
|
TO, CC, BCC,
|
||||||
@ -26,9 +22,9 @@ public abstract class Message implements Part, Body {
|
|||||||
|
|
||||||
protected String mUid;
|
protected String mUid;
|
||||||
|
|
||||||
protected HashSet<Flag> mFlags = new HashSet<Flag>();
|
private Set<Flag> mFlags = EnumSet.noneOf(Flag.class);
|
||||||
|
|
||||||
protected Date mInternalDate;
|
private Date mInternalDate;
|
||||||
|
|
||||||
protected Folder mFolder;
|
protected Folder mFolder;
|
||||||
|
|
||||||
@ -45,15 +41,15 @@ public abstract class Message implements Part, Body {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (o == null || !(o instanceof Message)) {
|
if (o == null || !(o instanceof Message)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Message other = (Message)o;
|
Message other = (Message)o;
|
||||||
return (mUid.equals(other.getUid())
|
return (getUid().equals(other.getUid())
|
||||||
&& mFolder.getName().equals(other.getFolder().getName())
|
&& getFolder().getName().equals(other.getFolder().getName()));
|
||||||
&& mFolder.getAccount().getUuid().equals(other.getFolder().getAccount().getUuid()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -62,7 +58,6 @@ public abstract class Message implements Part, Body {
|
|||||||
|
|
||||||
int result = 1;
|
int result = 1;
|
||||||
result = MULTIPLIER * result + mFolder.getName().hashCode();
|
result = MULTIPLIER * result + mFolder.getName().hashCode();
|
||||||
result = MULTIPLIER * result + mFolder.getAccount().getUuid().hashCode();
|
|
||||||
result = MULTIPLIER * result + mUid.hashCode();
|
result = MULTIPLIER * result + mUid.hashCode();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -72,7 +67,6 @@ public abstract class Message implements Part, Body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setUid(String uid) {
|
public void setUid(String uid) {
|
||||||
mReference = null;
|
|
||||||
this.mUid = uid;
|
this.mUid = uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +88,7 @@ public abstract class Message implements Part, Body {
|
|||||||
|
|
||||||
public abstract Date getSentDate();
|
public abstract Date getSentDate();
|
||||||
|
|
||||||
public abstract void setSentDate(Date sentDate) throws MessagingException;
|
public abstract void setSentDate(Date sentDate, boolean hideTimeZone) throws MessagingException;
|
||||||
|
|
||||||
public abstract Address[] getRecipients(RecipientType type) throws MessagingException;
|
public abstract Address[] getRecipients(RecipientType type) throws MessagingException;
|
||||||
|
|
||||||
@ -123,87 +117,43 @@ public abstract class Message implements Part, Body {
|
|||||||
|
|
||||||
public abstract void setReferences(String references) throws MessagingException;
|
public abstract void setReferences(String references) throws MessagingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
public abstract Body getBody();
|
public abstract Body getBody();
|
||||||
|
|
||||||
public abstract String getContentType() throws MessagingException;
|
@Override
|
||||||
|
|
||||||
public abstract void addHeader(String name, String value) throws MessagingException;
|
public abstract void addHeader(String name, String value) throws MessagingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void addRawHeader(String name, String raw) throws MessagingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
public abstract void setHeader(String name, String value) throws MessagingException;
|
public abstract void setHeader(String name, String value) throws MessagingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
public abstract String[] getHeader(String name) throws MessagingException;
|
public abstract String[] getHeader(String name) throws MessagingException;
|
||||||
|
|
||||||
public abstract Set<String> getHeaderNames() throws UnavailableStorageException;
|
public abstract Set<String> getHeaderNames() throws MessagingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
public abstract void removeHeader(String name) throws MessagingException;
|
public abstract void removeHeader(String name) throws MessagingException;
|
||||||
|
|
||||||
public abstract void setBody(Body body) throws MessagingException;
|
@Override
|
||||||
|
public abstract void setBody(Body body);
|
||||||
public boolean isMimeType(String mimeType) throws MessagingException {
|
|
||||||
return getContentType().startsWith(mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract long getId();
|
public abstract long getId();
|
||||||
|
|
||||||
public abstract String getPreview();
|
public abstract String getPreview();
|
||||||
public abstract boolean hasAttachments();
|
public abstract boolean hasAttachments();
|
||||||
|
|
||||||
/*
|
public abstract int getSize();
|
||||||
* calculateContentPreview
|
|
||||||
* Takes a plain text message body as a string.
|
|
||||||
* Returns a message summary as a string suitable for showing in a message list
|
|
||||||
*
|
|
||||||
* A message summary should be about the first 160 characters
|
|
||||||
* of unique text written by the message sender
|
|
||||||
* Quoted text, "On $date" and so on will be stripped out.
|
|
||||||
* All newlines and whitespace will be compressed.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static String calculateContentPreview(String text) {
|
|
||||||
if (text == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only look at the first 8k of a message when calculating
|
|
||||||
// the preview. This should avoid unnecessary
|
|
||||||
// memory usage on large messages
|
|
||||||
if (text.length() > 8192) {
|
|
||||||
text = text.substring(0, 8192);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove (correctly delimited by '-- \n') signatures
|
|
||||||
text = text.replaceAll("(?ms)^-- [\\r\\n]+.*", "");
|
|
||||||
// try to remove lines of dashes in the preview
|
|
||||||
text = text.replaceAll("(?m)^----.*?$", "");
|
|
||||||
// remove quoted text from the preview
|
|
||||||
text = text.replaceAll("(?m)^[#>].*$", "");
|
|
||||||
// Remove a common quote header from the preview
|
|
||||||
text = text.replaceAll("(?m)^On .*wrote.?$", "");
|
|
||||||
// Remove a more generic quote header from the preview
|
|
||||||
text = text.replaceAll("(?m)^.*\\w+:$", "");
|
|
||||||
// Remove horizontal rules.
|
|
||||||
text = text.replaceAll("\\s*([-=_]{30,}+)\\s*", " ");
|
|
||||||
|
|
||||||
// URLs in the preview should just be shown as "..." - They're not
|
|
||||||
// clickable and they usually overwhelm the preview
|
|
||||||
text = text.replaceAll("https?://\\S+", "...");
|
|
||||||
// Don't show newlines in the preview
|
|
||||||
text = text.replaceAll("(\\r|\\n)+", " ");
|
|
||||||
// Collapse whitespace in the preview
|
|
||||||
text = text.replaceAll("\\s+", " ");
|
|
||||||
// Remove any whitespace at the beginning and end of the string.
|
|
||||||
text = text.trim();
|
|
||||||
|
|
||||||
return (text.length() <= 512) ? text : text.substring(0, 512);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(String trashFolderName) throws MessagingException {}
|
public void delete(String trashFolderName) throws MessagingException {}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO Refactor Flags at some point to be able to store user defined flags.
|
* TODO Refactor Flags at some point to be able to store user defined flags.
|
||||||
*/
|
*/
|
||||||
public Flag[] getFlags() {
|
public Set<Flag> getFlags() {
|
||||||
return mFlags.toArray(EMPTY_FLAG_ARRAY);
|
return Collections.unmodifiableSet(mFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -227,7 +177,7 @@ public abstract class Message implements Part, Body {
|
|||||||
* @param flags
|
* @param flags
|
||||||
* @param set
|
* @param set
|
||||||
*/
|
*/
|
||||||
public void setFlags(Flag[] flags, boolean set) throws MessagingException {
|
public void setFlags(final Set<Flag> flags, boolean set) throws MessagingException {
|
||||||
for (Flag flag : flags) {
|
for (Flag flag : flags) {
|
||||||
setFlag(flag, set);
|
setFlag(flag, set);
|
||||||
}
|
}
|
||||||
@ -240,20 +190,11 @@ public abstract class Message implements Part, Body {
|
|||||||
|
|
||||||
public void destroy() throws MessagingException {}
|
public void destroy() throws MessagingException {}
|
||||||
|
|
||||||
public abstract void setEncoding(String encoding) throws UnavailableStorageException;
|
@Override
|
||||||
|
public abstract void setEncoding(String encoding) throws MessagingException;
|
||||||
|
|
||||||
public abstract void setCharset(String charset) throws MessagingException;
|
public abstract void setCharset(String charset) throws MessagingException;
|
||||||
|
|
||||||
public MessageReference makeMessageReference() {
|
|
||||||
if (mReference == null) {
|
|
||||||
mReference = new MessageReference();
|
|
||||||
mReference.accountUuid = getFolder().getAccount().getUuid();
|
|
||||||
mReference.folderName = getFolder().getName();
|
|
||||||
mReference.uid = mUid;
|
|
||||||
}
|
|
||||||
return mReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long calculateSize() {
|
public long calculateSize() {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@ -263,9 +204,9 @@ public abstract class Message implements Part, Body {
|
|||||||
eolOut.flush();
|
eolOut.flush();
|
||||||
return out.getCount();
|
return out.getCount();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(K9.LOG_TAG, "Failed to calculate a message size", e);
|
Log.e(LOG_TAG, "Failed to calculate a message size", e);
|
||||||
} catch (MessagingException e) {
|
} catch (MessagingException e) {
|
||||||
Log.e(K9.LOG_TAG, "Failed to calculate a message size", e);
|
Log.e(LOG_TAG, "Failed to calculate a message size", e);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -273,17 +214,15 @@ public abstract class Message implements Part, Body {
|
|||||||
/**
|
/**
|
||||||
* Copy the contents of this object into another {@code Message} object.
|
* Copy the contents of this object into another {@code Message} object.
|
||||||
*
|
*
|
||||||
* @param destination
|
* @param destination The {@code Message} object to receive the contents of this instance.
|
||||||
* The {@code Message} object to receive the contents of this instance.
|
|
||||||
*/
|
*/
|
||||||
protected void copy(Message destination) {
|
protected void copy(Message destination) {
|
||||||
destination.mUid = mUid;
|
destination.mUid = mUid;
|
||||||
destination.mInternalDate = mInternalDate;
|
destination.mInternalDate = mInternalDate;
|
||||||
destination.mFolder = mFolder;
|
destination.mFolder = mFolder;
|
||||||
destination.mReference = mReference;
|
|
||||||
|
|
||||||
// mFlags contents can change during the object lifetime, so copy the Set
|
// mFlags contents can change during the object lifetime, so copy the Set
|
||||||
destination.mFlags = new HashSet<Flag>(mFlags);
|
destination.mFlags = EnumSet.copyOf(mFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -297,5 +236,7 @@ public abstract class Message implements Part, Body {
|
|||||||
* for more information.
|
* for more information.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public abstract Message clone();
|
public abstract Message clone();
|
||||||
|
|
||||||
}
|
}
|
@ -1,12 +1,11 @@
|
|||||||
|
|
||||||
package com.fsck.k9.controller;
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
import com.fsck.k9.mail.Message;
|
|
||||||
|
|
||||||
public interface MessageRetrievalListener {
|
public interface MessageRetrievalListener<T extends Message> {
|
||||||
public void messageStarted(String uid, int number, int ofTotal);
|
public void messageStarted(String uid, int number, int ofTotal);
|
||||||
|
|
||||||
public void messageFinished(Message message, int number, int ofTotal);
|
public void messageFinished(T message, int number, int ofTotal);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIXME <strong>this method is almost never invoked by various Stores! Don't rely on it unless fixed!!</strong>
|
* FIXME <strong>this method is almost never invoked by various Stores! Don't rely on it unless fixed!!</strong>
|
@ -4,7 +4,7 @@ package com.fsck.k9.mail;
|
|||||||
public class MessagingException extends Exception {
|
public class MessagingException extends Exception {
|
||||||
public static final long serialVersionUID = -1;
|
public static final long serialVersionUID = -1;
|
||||||
|
|
||||||
boolean permanentFailure = false;
|
private boolean permanentFailure = false;
|
||||||
|
|
||||||
public MessagingException(String message) {
|
public MessagingException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
@ -28,9 +28,9 @@ public class MessagingException extends Exception {
|
|||||||
return permanentFailure;
|
return permanentFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO setters in Exception are bad style, remove (it's nearly unused anyway)
|
||||||
public void setPermanentFailure(boolean permanentFailure) {
|
public void setPermanentFailure(boolean permanentFailure) {
|
||||||
this.permanentFailure = permanentFailure;
|
this.permanentFailure = permanentFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
72
k9mail-library/src/main/java/com/fsck/k9/mail/Multipart.java
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
|
||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.james.mime4j.util.MimeUtil;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.internet.CharsetSupport;
|
||||||
|
import com.fsck.k9.mail.internet.TextBody;
|
||||||
|
|
||||||
|
public abstract class Multipart implements CompositeBody {
|
||||||
|
private Part mParent;
|
||||||
|
|
||||||
|
private final List<BodyPart> mParts = new ArrayList<BodyPart>();
|
||||||
|
|
||||||
|
public void addBodyPart(BodyPart part) {
|
||||||
|
mParts.add(part);
|
||||||
|
part.setParent(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BodyPart getBodyPart(int index) {
|
||||||
|
return mParts.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BodyPart> getBodyParts() {
|
||||||
|
return Collections.unmodifiableList(mParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String getMimeType();
|
||||||
|
|
||||||
|
public abstract String getBoundary();
|
||||||
|
|
||||||
|
public int getCount() {
|
||||||
|
return mParts.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Part getParent() {
|
||||||
|
return mParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParent(Part parent) {
|
||||||
|
this.mParent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEncoding(String encoding) throws MessagingException {
|
||||||
|
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
|
||||||
|
&& !MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
|
||||||
|
throw new MessagingException(
|
||||||
|
"Incompatible content-transfer-encoding applied to a CompositeBody");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nothing else to do. Each subpart has its own separate encoding */
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCharset(String charset) throws MessagingException {
|
||||||
|
if (mParts.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
BodyPart part = mParts.get(0);
|
||||||
|
Body body = part.getBody();
|
||||||
|
if (body instanceof TextBody) {
|
||||||
|
CharsetSupport.setCharset(charset, part);
|
||||||
|
((TextBody)body).setCharset(charset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract byte[] getPreamble();
|
||||||
|
public abstract byte[] getEpilogue();
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for some of
|
||||||
|
* https://developer.android.com/reference/android/net/ConnectivityManager.html#TYPE_MOBILE etc.
|
||||||
|
*/
|
||||||
|
public enum NetworkType {
|
||||||
|
|
||||||
|
WIFI,
|
||||||
|
MOBILE,
|
||||||
|
OTHER;
|
||||||
|
|
||||||
|
public static NetworkType fromConnectivityManagerType(int type){
|
||||||
|
switch (type) {
|
||||||
|
case ConnectivityManager.TYPE_MOBILE:
|
||||||
|
return MOBILE;
|
||||||
|
case ConnectivityManager.TYPE_WIFI:
|
||||||
|
return WIFI;
|
||||||
|
default:
|
||||||
|
return OTHER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
k9mail-library/src/main/java/com/fsck/k9/mail/Part.java
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
|
||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public interface Part {
|
||||||
|
void addHeader(String name, String value) throws MessagingException;
|
||||||
|
|
||||||
|
void addRawHeader(String name, String raw) throws MessagingException;
|
||||||
|
|
||||||
|
void removeHeader(String name) throws MessagingException;
|
||||||
|
|
||||||
|
void setHeader(String name, String value) throws MessagingException;
|
||||||
|
|
||||||
|
Body getBody();
|
||||||
|
|
||||||
|
String getContentType();
|
||||||
|
|
||||||
|
String getDisposition() throws MessagingException;
|
||||||
|
|
||||||
|
String getContentId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of headers of the given name. The array may be empty.
|
||||||
|
*/
|
||||||
|
String[] getHeader(String name) throws MessagingException;
|
||||||
|
|
||||||
|
boolean isMimeType(String mimeType) throws MessagingException;
|
||||||
|
|
||||||
|
String getMimeType();
|
||||||
|
|
||||||
|
void setBody(Body body);
|
||||||
|
|
||||||
|
void writeTo(OutputStream out) throws IOException, MessagingException;
|
||||||
|
|
||||||
|
void writeHeaderTo(OutputStream out) throws IOException, MessagingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called just prior to transmission, once the type of transport is known to
|
||||||
|
* be 7bit.
|
||||||
|
* <p>
|
||||||
|
* All bodies that are 8bit will be converted to 7bit and recursed if of
|
||||||
|
* type {@link CompositeBody}, or will be converted to quoted-printable in all other
|
||||||
|
* cases. Bodies with encodings other than 8bit remain unchanged.
|
||||||
|
*
|
||||||
|
* @throws MessagingException
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
//TODO perhaps it would be clearer to use a flag "force7bit" in writeTo
|
||||||
|
void setUsing7bitTransport() throws MessagingException;
|
||||||
|
|
||||||
|
String getServerExtra();
|
||||||
|
|
||||||
|
void setServerExtra(String serverExtra);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.power.TracingPowerManager.TracingWakeLock;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public interface PushReceiver {
|
||||||
|
Context getContext();
|
||||||
|
void syncFolder(Folder folder);
|
||||||
|
void messagesArrived(Folder folder, List<Message> mess);
|
||||||
|
void messagesFlagsChanged(Folder folder, List<Message> mess);
|
||||||
|
void messagesRemoved(Folder folder, List<Message> mess);
|
||||||
|
String getPushState(String folderName);
|
||||||
|
void pushError(String errorMessage, Exception e);
|
||||||
|
void setPushActive(String folderName, boolean enabled);
|
||||||
|
void sleep(TracingWakeLock wakeLock, long millis);
|
||||||
|
}
|
@ -3,7 +3,6 @@ package com.fsck.k9.mail;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import com.fsck.k9.Account;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an abstraction to get rid of the store- and transport-specific URIs.
|
* This is an abstraction to get rid of the store- and transport-specific URIs.
|
||||||
@ -13,14 +12,40 @@ import com.fsck.k9.Account;
|
|||||||
* store/transport URIs altogether.
|
* store/transport URIs altogether.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @see Account#getStoreUri()
|
* @see com.fsck.k9.mail.store.StoreConfig#getStoreUri()
|
||||||
* @see Account#getTransportUri()
|
* @see com.fsck.k9.mail.store.StoreConfig#getTransportUri()
|
||||||
*/
|
*/
|
||||||
public class ServerSettings {
|
public class ServerSettings {
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
|
||||||
|
IMAP(143, 993),
|
||||||
|
SMTP(587, 465),
|
||||||
|
WebDAV(80, 443),
|
||||||
|
POP3(110, 995);
|
||||||
|
|
||||||
|
public final int defaultPort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: port for connections using TLS (=SSL) immediately
|
||||||
|
* from the initial TCP connection.
|
||||||
|
*
|
||||||
|
* STARTTLS uses the defaultPort, then upgrades.
|
||||||
|
*
|
||||||
|
* See https://www.fastmail.com/help/technical/ssltlsstarttls.html.
|
||||||
|
*/
|
||||||
|
public final int defaultTlsPort;
|
||||||
|
|
||||||
|
private Type(int defaultPort, int defaultTlsPort) {
|
||||||
|
this.defaultPort = defaultPort;
|
||||||
|
this.defaultTlsPort = defaultTlsPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the store or transport type (e.g. "IMAP").
|
* Name of the store or transport type (e.g. IMAP).
|
||||||
*/
|
*/
|
||||||
public final String type;
|
public final Type type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The host name of the server.
|
* The host name of the server.
|
||||||
@ -48,7 +73,7 @@ public class ServerSettings {
|
|||||||
*
|
*
|
||||||
* {@code null} if not applicable for the store or transport.
|
* {@code null} if not applicable for the store or transport.
|
||||||
*/
|
*/
|
||||||
public final String authenticationType;
|
public final AuthType authenticationType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The username part of the credentials needed to authenticate to the server.
|
* The username part of the credentials needed to authenticate to the server.
|
||||||
@ -64,6 +89,14 @@ public class ServerSettings {
|
|||||||
*/
|
*/
|
||||||
public final String password;
|
public final String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alias to retrieve a client certificate using Android 4.0 KeyChain API
|
||||||
|
* for TLS client certificate authentication with the server.
|
||||||
|
*
|
||||||
|
* {@code null} if not applicable for the store or transport.
|
||||||
|
*/
|
||||||
|
public final String clientCertificateAlias;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store- or transport-specific settings as key/value pair.
|
* Store- or transport-specific settings as key/value pair.
|
||||||
*
|
*
|
||||||
@ -89,10 +122,12 @@ public class ServerSettings {
|
|||||||
* see {@link ServerSettings#username}
|
* see {@link ServerSettings#username}
|
||||||
* @param password
|
* @param password
|
||||||
* see {@link ServerSettings#password}
|
* see {@link ServerSettings#password}
|
||||||
|
* @param clientCertificateAlias
|
||||||
|
* see {@link ServerSettings#clientCertificateAlias}
|
||||||
*/
|
*/
|
||||||
public ServerSettings(String type, String host, int port,
|
public ServerSettings(Type type, String host, int port,
|
||||||
ConnectionSecurity connectionSecurity, String authenticationType, String username,
|
ConnectionSecurity connectionSecurity, AuthType authenticationType, String username,
|
||||||
String password) {
|
String password, String clientCertificateAlias) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
@ -100,6 +135,7 @@ public class ServerSettings {
|
|||||||
this.authenticationType = authenticationType;
|
this.authenticationType = authenticationType;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
|
this.clientCertificateAlias = clientCertificateAlias;
|
||||||
this.extra = null;
|
this.extra = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,12 +156,14 @@ public class ServerSettings {
|
|||||||
* see {@link ServerSettings#username}
|
* see {@link ServerSettings#username}
|
||||||
* @param password
|
* @param password
|
||||||
* see {@link ServerSettings#password}
|
* see {@link ServerSettings#password}
|
||||||
|
* @param clientCertificateAlias
|
||||||
|
* see {@link ServerSettings#clientCertificateAlias}
|
||||||
* @param extra
|
* @param extra
|
||||||
* see {@link ServerSettings#extra}
|
* see {@link ServerSettings#extra}
|
||||||
*/
|
*/
|
||||||
public ServerSettings(String type, String host, int port,
|
public ServerSettings(Type type, String host, int port,
|
||||||
ConnectionSecurity connectionSecurity, String authenticationType, String username,
|
ConnectionSecurity connectionSecurity, AuthType authenticationType, String username,
|
||||||
String password, Map<String, String> extra) {
|
String password, String clientCertificateAlias, Map<String, String> extra) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
@ -133,6 +171,7 @@ public class ServerSettings {
|
|||||||
this.authenticationType = authenticationType;
|
this.authenticationType = authenticationType;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
|
this.clientCertificateAlias = clientCertificateAlias;
|
||||||
this.extra = (extra != null) ?
|
this.extra = (extra != null) ?
|
||||||
Collections.unmodifiableMap(new HashMap<String, String>(extra)) : null;
|
Collections.unmodifiableMap(new HashMap<String, String>(extra)) : null;
|
||||||
}
|
}
|
||||||
@ -145,7 +184,7 @@ public class ServerSettings {
|
|||||||
* @param type
|
* @param type
|
||||||
* see {@link ServerSettings#type}
|
* see {@link ServerSettings#type}
|
||||||
*/
|
*/
|
||||||
public ServerSettings(String type) {
|
public ServerSettings(Type type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
host = null;
|
host = null;
|
||||||
port = -1;
|
port = -1;
|
||||||
@ -153,6 +192,7 @@ public class ServerSettings {
|
|||||||
authenticationType = null;
|
authenticationType = null;
|
||||||
username = null;
|
username = null;
|
||||||
password = null;
|
password = null;
|
||||||
|
clientCertificateAlias = null;
|
||||||
extra = null;
|
extra = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,6 +213,11 @@ public class ServerSettings {
|
|||||||
|
|
||||||
public ServerSettings newPassword(String newPassword) {
|
public ServerSettings newPassword(String newPassword) {
|
||||||
return new ServerSettings(type, host, port, connectionSecurity, authenticationType,
|
return new ServerSettings(type, host, port, connectionSecurity, authenticationType,
|
||||||
username, newPassword);
|
username, newPassword, clientCertificateAlias);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerSettings newClientCertificateAlias(String newAlias) {
|
||||||
|
return new ServerSettings(type, host, port, connectionSecurity, AuthType.EXTERNAL,
|
||||||
|
username, password, newAlias);
|
||||||
}
|
}
|
||||||
}
|
}
|
69
k9mail-library/src/main/java/com/fsck/k9/mail/Store.java
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
|
||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store is the access point for an email message store. It's location can be
|
||||||
|
* local or remote and no specific protocol is defined. Store is intended to
|
||||||
|
* loosely model in combination the JavaMail classes javax.mail.Store and
|
||||||
|
* javax.mail.Folder along with some additional functionality to improve
|
||||||
|
* performance on mobile devices. Implementations of this class should focus on
|
||||||
|
* making as few network connections as possible.
|
||||||
|
*/
|
||||||
|
public abstract class Store {
|
||||||
|
public abstract Folder getFolder(String name);
|
||||||
|
|
||||||
|
public abstract List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException;
|
||||||
|
|
||||||
|
public abstract void checkSettings() throws MessagingException;
|
||||||
|
|
||||||
|
public boolean isCopyCapable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMoveCapable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPushCapable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSendCapable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExpungeCapable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSeenFlagSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMessages(List<? extends Message> messages) throws MessagingException { }
|
||||||
|
|
||||||
|
public Pusher getPusher(PushReceiver receiver) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String decodeUtf8(String s) {
|
||||||
|
try {
|
||||||
|
return URLDecoder.decode(s, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException("UTF-8 not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String encodeUtf8(String s) {
|
||||||
|
try {
|
||||||
|
return URLEncoder.encode(s, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException("UTF-8 not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,30 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.ssl.DefaultTrustedSocketFactory;
|
||||||
|
import com.fsck.k9.mail.store.StoreConfig;
|
||||||
|
import com.fsck.k9.mail.ServerSettings.Type;
|
||||||
import com.fsck.k9.mail.transport.SmtpTransport;
|
import com.fsck.k9.mail.transport.SmtpTransport;
|
||||||
import com.fsck.k9.mail.transport.WebDavTransport;
|
import com.fsck.k9.mail.transport.WebDavTransport;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
public abstract class Transport {
|
public abstract class Transport {
|
||||||
protected static final int SOCKET_CONNECT_TIMEOUT = 10000;
|
protected static final int SOCKET_CONNECT_TIMEOUT = 10000;
|
||||||
|
|
||||||
// RFC 1047
|
// RFC 1047
|
||||||
protected static final int SOCKET_READ_TIMEOUT = 300000;
|
protected static final int SOCKET_READ_TIMEOUT = 300000;
|
||||||
|
|
||||||
public synchronized static Transport getInstance(Account account) throws MessagingException {
|
public synchronized static Transport getInstance(Context context, StoreConfig storeConfig) throws MessagingException {
|
||||||
String uri = account.getTransportUri();
|
String uri = storeConfig.getTransportUri();
|
||||||
if (uri.startsWith("smtp")) {
|
if (uri.startsWith("smtp")) {
|
||||||
return new SmtpTransport(account);
|
return new SmtpTransport(storeConfig, new DefaultTrustedSocketFactory(context));
|
||||||
} else if (uri.startsWith("webdav")) {
|
} else if (uri.startsWith("webdav")) {
|
||||||
return new WebDavTransport(account);
|
return new WebDavTransport(storeConfig);
|
||||||
} else {
|
} else {
|
||||||
throw new MessagingException("Unable to locate an applicable Transport for " + uri);
|
throw new MessagingException("Unable to locate an applicable Transport for " + uri);
|
||||||
}
|
}
|
||||||
@ -56,9 +64,9 @@ public abstract class Transport {
|
|||||||
* @see WebDavTransport#createUri(ServerSettings)
|
* @see WebDavTransport#createUri(ServerSettings)
|
||||||
*/
|
*/
|
||||||
public static String createTransportUri(ServerSettings server) {
|
public static String createTransportUri(ServerSettings server) {
|
||||||
if (SmtpTransport.TRANSPORT_TYPE.equals(server.type)) {
|
if (Type.SMTP == server.type) {
|
||||||
return SmtpTransport.createUri(server);
|
return SmtpTransport.createUri(server);
|
||||||
} else if (WebDavTransport.TRANSPORT_TYPE.equals(server.type)) {
|
} else if (Type.WebDAV == server.type) {
|
||||||
return WebDavTransport.createUri(server);
|
return WebDavTransport.createUri(server);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Not a valid transport URI");
|
throw new IllegalArgumentException("Not a valid transport URI");
|
||||||
@ -71,4 +79,19 @@ public abstract class Transport {
|
|||||||
public abstract void sendMessage(Message message) throws MessagingException;
|
public abstract void sendMessage(Message message) throws MessagingException;
|
||||||
|
|
||||||
public abstract void close();
|
public abstract void close();
|
||||||
|
|
||||||
|
protected static String encodeUtf8(String s) {
|
||||||
|
try {
|
||||||
|
return URLEncoder.encode(s, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException("UTF-8 not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected static String decodeUtf8(String s) {
|
||||||
|
try {
|
||||||
|
return URLDecoder.decode(s, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException("UTF-8 not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -17,8 +17,8 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail.filter;
|
package com.fsck.k9.mail.filter;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides Base64 encoding and decoding as defined by RFC 2045.
|
* Provides Base64 encoding and decoding as defined by RFC 2045.
|
||||||
@ -34,6 +34,22 @@ import java.math.BigInteger;
|
|||||||
* @version $Id$
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
public class Base64 {
|
public class Base64 {
|
||||||
|
public static String decode(String encoded) {
|
||||||
|
if (encoded == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] decoded = new Base64().decode(encoded.getBytes());
|
||||||
|
return new String(decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encode(String s) {
|
||||||
|
if (s == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] encoded = new Base64().encode(s.getBytes());
|
||||||
|
return new String(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chunk size per RFC 2045 section 6.8.
|
* Chunk size per RFC 2045 section 6.8.
|
||||||
*
|
*
|
||||||
@ -225,12 +241,8 @@ public class Base64 {
|
|||||||
}
|
}
|
||||||
this.decodeSize = encodeSize - 1;
|
this.decodeSize = encodeSize - 1;
|
||||||
if (containsBase64Byte(lineSeparator)) {
|
if (containsBase64Byte(lineSeparator)) {
|
||||||
String sep;
|
String sep = new String(lineSeparator, Charset.forName("UTF-8"));
|
||||||
try {
|
|
||||||
sep = new String(lineSeparator, "UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException uee) {
|
|
||||||
sep = new String(lineSeparator);
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]");
|
throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,8 +5,11 @@ import java.io.IOException;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
public class EOLConvertingOutputStream extends FilterOutputStream {
|
public class EOLConvertingOutputStream extends FilterOutputStream {
|
||||||
|
private static final int CR = '\r';
|
||||||
|
private static final int LF = '\n';
|
||||||
private int lastChar;
|
private int lastChar;
|
||||||
private boolean ignoreNextIfLF = false;
|
private static final int IGNORE_LF = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
|
||||||
public EOLConvertingOutputStream(OutputStream out) {
|
public EOLConvertingOutputStream(OutputStream out) {
|
||||||
super(out);
|
super(out);
|
||||||
@ -14,26 +17,27 @@ public class EOLConvertingOutputStream extends FilterOutputStream {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(int oneByte) throws IOException {
|
public void write(int oneByte) throws IOException {
|
||||||
if (!ignoreNextIfLF) {
|
if (oneByte == LF && lastChar == IGNORE_LF) {
|
||||||
if ((oneByte == '\n') && (lastChar != '\r')) {
|
lastChar = LF;
|
||||||
super.write('\r');
|
return;
|
||||||
}
|
|
||||||
super.write(oneByte);
|
|
||||||
lastChar = oneByte;
|
|
||||||
}
|
}
|
||||||
ignoreNextIfLF = false;
|
if (oneByte == LF && lastChar != CR) {
|
||||||
|
super.write(CR);
|
||||||
|
} else if (oneByte != LF && lastChar == CR) {
|
||||||
|
super.write(LF);
|
||||||
|
}
|
||||||
|
super.write(oneByte);
|
||||||
|
lastChar = oneByte;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush() throws IOException {
|
public void flush() throws IOException {
|
||||||
if (lastChar == '\r') {
|
if (lastChar == CR) {
|
||||||
super.write('\n');
|
super.write(LF);
|
||||||
lastChar = '\n';
|
|
||||||
|
|
||||||
// We have to ignore the next character if it is <LF>. Otherwise it
|
// We have to ignore the next character if it is <LF>. Otherwise it
|
||||||
// will be expanded to an additional <CR><LF> sequence although it
|
// will be expanded to an additional <CR><LF> sequence although it
|
||||||
// belongs to the one just completed.
|
// belongs to the one just completed.
|
||||||
ignoreNextIfLF = true;
|
lastChar = IGNORE_LF;
|
||||||
}
|
}
|
||||||
super.flush();
|
super.flush();
|
||||||
}
|
}
|
@ -3,6 +3,7 @@ package com.fsck.k9.mail.filter;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A filtering InputStream that stops allowing reads after the given length has been read. This
|
* A filtering InputStream that stops allowing reads after the given length has been read. This
|
||||||
@ -66,6 +67,6 @@ public class FixedLengthInputStream extends InputStream {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("FixedLengthInputStream(in=%s, length=%d)", mIn.toString(), mLength);
|
return String.format(Locale.US, "FixedLengthInputStream(in=%s, length=%d)", mIn.toString(), mLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -30,13 +30,13 @@ public class Hex {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an array of bytes into an array of characters representing the hexidecimal values of each byte in order.
|
* Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
|
||||||
* The returned array will be double the length of the passed array, as it takes two characters to represent any
|
* The returned array will be double the length of the passed array, as it takes two characters to represent any
|
||||||
* given byte.
|
* given byte.
|
||||||
*
|
*
|
||||||
* @param data
|
* @param data
|
||||||
* a byte[] to convert to Hex characters
|
* a byte[] to convert to Hex characters
|
||||||
* @return A char[] containing hexidecimal characters
|
* @return A char[] containing lower-case hexadecimal characters
|
||||||
*/
|
*/
|
||||||
public static char[] encodeHex(byte[] data) {
|
public static char[] encodeHex(byte[] data) {
|
||||||
|
|
@ -3,6 +3,7 @@ package com.fsck.k9.mail.filter;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A filtering InputStream that allows single byte "peeks" without consuming the byte. The
|
* A filtering InputStream that allows single byte "peeks" without consuming the byte. The
|
||||||
@ -59,7 +60,7 @@ public class PeekableInputStream extends InputStream {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("PeekableInputStream(in=%s, peeked=%b, peekedByte=%d)",
|
return String.format(Locale.US, "PeekableInputStream(in=%s, peeked=%b, peekedByte=%d)",
|
||||||
mIn.toString(), mPeeked, mPeekedByte);
|
mIn.toString(), mPeeked, mPeekedByte);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.MessagingException;
|
||||||
|
import com.fsck.k9.mail.filter.Base64OutputStream;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
|
||||||
|
import org.apache.james.mime4j.util.MimeUtil;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Body that is backed by a temp file. The Body exposes a getOutputStream method that allows
|
||||||
|
* the user to write to the temp file. After the write the body is available via getInputStream
|
||||||
|
* and writeTo one time. After writeTo is called, or the InputStream returned from
|
||||||
|
* getInputStream is closed the file is deleted and the Body should be considered disposed of.
|
||||||
|
*/
|
||||||
|
public class BinaryTempFileBody implements RawDataBody, SizeAware {
|
||||||
|
private static File mTempDirectory;
|
||||||
|
|
||||||
|
private File mFile;
|
||||||
|
|
||||||
|
String mEncoding = null;
|
||||||
|
|
||||||
|
public static void setTempDirectory(File tempDirectory) {
|
||||||
|
mTempDirectory = tempDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getTempDirectory() {
|
||||||
|
return mTempDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEncoding() {
|
||||||
|
return mEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEncoding(String encoding) throws MessagingException {
|
||||||
|
if (mEncoding != null && mEncoding.equalsIgnoreCase(encoding)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The encoding changed, so we need to convert the message
|
||||||
|
if (!MimeUtil.ENC_8BIT.equalsIgnoreCase(mEncoding)) {
|
||||||
|
throw new RuntimeException("Can't convert from encoding: " + mEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
File newFile = File.createTempFile("body", null, mTempDirectory);
|
||||||
|
final OutputStream out = new FileOutputStream(newFile);
|
||||||
|
try {
|
||||||
|
OutputStream wrappedOut = null;
|
||||||
|
if (MimeUtil.ENC_QUOTED_PRINTABLE.equals(encoding)) {
|
||||||
|
wrappedOut = new QuotedPrintableOutputStream(out, false);
|
||||||
|
} else if (MimeUtil.ENC_BASE64.equals(encoding)) {
|
||||||
|
wrappedOut = new Base64OutputStream(out);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Target encoding not supported: " + encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream in = getInputStream();
|
||||||
|
try {
|
||||||
|
IOUtils.copy(in, wrappedOut);
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(in);
|
||||||
|
IOUtils.closeQuietly(wrappedOut);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
mFile = newFile;
|
||||||
|
mEncoding = encoding;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MessagingException("Unable to convert body", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BinaryTempFileBody(String encoding) {
|
||||||
|
if (mTempDirectory == null) {
|
||||||
|
throw new RuntimeException("setTempDirectory has not been called on BinaryTempFileBody!");
|
||||||
|
}
|
||||||
|
|
||||||
|
mEncoding = encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream getOutputStream() throws IOException {
|
||||||
|
mFile = File.createTempFile("body", null, mTempDirectory);
|
||||||
|
mFile.deleteOnExit();
|
||||||
|
return new FileOutputStream(mFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream getInputStream() throws MessagingException {
|
||||||
|
try {
|
||||||
|
return new BinaryTempFileBodyInputStream(new FileInputStream(mFile));
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new MessagingException("Unable to open body", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||||
|
InputStream in = getInputStream();
|
||||||
|
try {
|
||||||
|
IOUtils.copy(in, out);
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSize() {
|
||||||
|
return mFile.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getFile() {
|
||||||
|
return mFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BinaryTempFileBodyInputStream extends FilterInputStream {
|
||||||
|
public BinaryTempFileBodyInputStream(InputStream in) {
|
||||||
|
super(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
try {
|
||||||
|
super.close();
|
||||||
|
} finally {
|
||||||
|
mFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeWithoutDeleting() throws IOException {
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.james.mime4j.util.MimeUtil;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.CompositeBody;
|
||||||
|
import com.fsck.k9.mail.MessagingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link BinaryTempFileBody} extension containing a body of type message/rfc822.
|
||||||
|
*/
|
||||||
|
public class BinaryTempFileMessageBody extends BinaryTempFileBody implements CompositeBody {
|
||||||
|
|
||||||
|
public BinaryTempFileMessageBody(String encoding) {
|
||||||
|
super(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEncoding(String encoding) throws MessagingException {
|
||||||
|
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
|
||||||
|
&& !MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
|
||||||
|
throw new MessagingException(
|
||||||
|
"Incompatible content-transfer-encoding applied to a CompositeBody");
|
||||||
|
}
|
||||||
|
mEncoding = encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||||
|
InputStream in = getInputStream();
|
||||||
|
try {
|
||||||
|
if (MimeUtil.ENC_7BIT.equalsIgnoreCase(mEncoding)) {
|
||||||
|
/*
|
||||||
|
* If we knew the message was already 7bit clean, then it
|
||||||
|
* could be sent along without processing. But since we
|
||||||
|
* don't know, we recursively parse it.
|
||||||
|
*/
|
||||||
|
MimeMessage message = new MimeMessage(in, true);
|
||||||
|
message.setUsing7bitTransport();
|
||||||
|
message.writeTo(out);
|
||||||
|
} else {
|
||||||
|
IOUtils.copy(in, out);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUsing7bitTransport() throws MessagingException {
|
||||||
|
/*
|
||||||
|
* There's nothing to recurse into here, so there's nothing to do.
|
||||||
|
* The enclosing BodyPart already called setEncoding(MimeUtil.ENC_7BIT). Once
|
||||||
|
* writeTo() is called, the file with the rfc822 body will be opened
|
||||||
|
* for reading and will then be recursed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -2,16 +2,18 @@
|
|||||||
package com.fsck.k9.mail.internet;
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.fsck.k9.K9;
|
|
||||||
import com.fsck.k9.mail.Message;
|
import com.fsck.k9.mail.Message;
|
||||||
import com.fsck.k9.mail.MessagingException;
|
import com.fsck.k9.mail.MessagingException;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
import org.apache.james.mime4j.codec.Base64InputStream;
|
import org.apache.james.mime4j.codec.Base64InputStream;
|
||||||
import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
|
import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
|
||||||
import org.apache.james.mime4j.util.CharsetUtil;
|
import org.apache.james.mime4j.util.CharsetUtil;
|
||||||
|
|
||||||
|
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static methods for decoding strings, byte arrays and encoded words.
|
* Static methods for decoding strings, byte arrays and encoded words.
|
||||||
@ -20,7 +22,7 @@ import org.apache.james.mime4j.util.CharsetUtil;
|
|||||||
* decode emoji characters in the Subject headers. The method to decode emoji depends on the MimeMessage class because
|
* decode emoji characters in the Subject headers. The method to decode emoji depends on the MimeMessage class because
|
||||||
* it has to be determined with the sender address, the mailer and so on.
|
* it has to be determined with the sender address, the mailer and so on.
|
||||||
*/
|
*/
|
||||||
public class DecoderUtil {
|
class DecoderUtil {
|
||||||
/**
|
/**
|
||||||
* Decodes an encoded word encoded with the 'B' encoding (described in
|
* Decodes an encoded word encoded with the 'B' encoding (described in
|
||||||
* RFC 2047) found in a header field body.
|
* RFC 2047) found in a header field body.
|
||||||
@ -30,16 +32,11 @@ public class DecoderUtil {
|
|||||||
* @return the decoded string.
|
* @return the decoded string.
|
||||||
*/
|
*/
|
||||||
private static String decodeB(String encodedWord, String charset) {
|
private static String decodeB(String encodedWord, String charset) {
|
||||||
byte[] bytes;
|
byte[] bytes = encodedWord.getBytes(Charset.forName("US-ASCII"));
|
||||||
try {
|
|
||||||
bytes = encodedWord.getBytes("US-ASCII");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Base64InputStream is = new Base64InputStream(new ByteArrayInputStream(bytes));
|
Base64InputStream is = new Base64InputStream(new ByteArrayInputStream(bytes));
|
||||||
try {
|
try {
|
||||||
return MimeUtility.readToString(is, charset);
|
return CharsetSupport.readToString(is, charset);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -68,16 +65,11 @@ public class DecoderUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] bytes;
|
byte[] bytes = sb.toString().getBytes(Charset.forName("US-ASCII"));
|
||||||
try {
|
|
||||||
bytes = sb.toString().getBytes("US-ASCII");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
QuotedPrintableInputStream is = new QuotedPrintableInputStream(new ByteArrayInputStream(bytes));
|
QuotedPrintableInputStream is = new QuotedPrintableInputStream(new ByteArrayInputStream(bytes));
|
||||||
try {
|
try {
|
||||||
return MimeUtility.readToString(is, charset);
|
return CharsetSupport.readToString(is, charset);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -110,24 +102,28 @@ public class DecoderUtil {
|
|||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int begin = body.indexOf("=?", previousEnd);
|
int begin = body.indexOf("=?", previousEnd);
|
||||||
|
if (begin == -1) {
|
||||||
|
sb.append(body.substring(previousEnd));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
// ANDROID: The mime4j original version has an error here. It gets confused if
|
// ANDROID: The mime4j original version has an error here. It gets confused if
|
||||||
// the encoded string begins with an '=' (just after "?Q?"). This patch seeks forward
|
// the encoded string begins with an '=' (just after "?Q?"). This patch seeks forward
|
||||||
// to find the two '?' in the "header", before looking for the final "?=".
|
// to find the two '?' in the "header", before looking for the final "?=".
|
||||||
int endScan = begin + 2;
|
int qm1 = body.indexOf('?', begin + 2);
|
||||||
if (begin != -1) {
|
if (qm1 == -1) {
|
||||||
int qm1 = body.indexOf('?', endScan + 2);
|
sb.append(body.substring(previousEnd));
|
||||||
int qm2 = body.indexOf('?', qm1 + 1);
|
return sb.toString();
|
||||||
if (qm2 != -1) {
|
|
||||||
endScan = qm2 + 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int end = begin == -1 ? -1 : body.indexOf("?=", endScan);
|
int qm2 = body.indexOf('?', qm1 + 1);
|
||||||
if (end == -1) {
|
if (qm2 == -1) {
|
||||||
if (previousEnd == 0)
|
sb.append(body.substring(previousEnd));
|
||||||
return body;
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
int end = body.indexOf("?=", qm2 + 1);
|
||||||
|
if (end == -1) {
|
||||||
sb.append(body.substring(previousEnd));
|
sb.append(body.substring(previousEnd));
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
@ -167,13 +163,13 @@ public class DecoderUtil {
|
|||||||
|
|
||||||
String charset;
|
String charset;
|
||||||
try {
|
try {
|
||||||
charset = MimeUtility.fixupCharset(mimeCharset, message);
|
charset = CharsetSupport.fixupCharset(mimeCharset, message);
|
||||||
} catch (MessagingException e) {
|
} catch (MessagingException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encodedText.length() == 0) {
|
if (encodedText.isEmpty()) {
|
||||||
Log.w(K9.LOG_TAG, "Missing encoded text in encoded word: '" + body.substring(begin, end) + "'");
|
Log.w(LOG_TAG, "Missing encoded text in encoded word: '" + body.substring(begin, end) + "'");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +178,7 @@ public class DecoderUtil {
|
|||||||
} else if (encoding.equalsIgnoreCase("B")) {
|
} else if (encoding.equalsIgnoreCase("B")) {
|
||||||
return DecoderUtil.decodeB(encodedText, charset);
|
return DecoderUtil.decodeB(encodedText, charset);
|
||||||
} else {
|
} else {
|
||||||
Log.w(K9.LOG_TAG, "Warning: Unknown encoding in encoded word '" + body.substring(begin, end) + "'");
|
Log.w(LOG_TAG, "Warning: Unknown encoding in encoded word '" + body.substring(begin, end) + "'");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,7 +16,7 @@ import org.apache.james.mime4j.util.CharsetUtil;
|
|||||||
* encode emoji characters in the Subject headers. The method to decode emoji depends on the MimeMessage class because
|
* encode emoji characters in the Subject headers. The method to decode emoji depends on the MimeMessage class because
|
||||||
* it has to be determined with the sender address.
|
* it has to be determined with the sender address.
|
||||||
*/
|
*/
|
||||||
public class EncoderUtil {
|
class EncoderUtil {
|
||||||
private static final BitSet Q_RESTRICTED_CHARS = initChars("=_?\"#$%&'(),.:;<>@[\\]^`{|}~");
|
private static final BitSet Q_RESTRICTED_CHARS = initChars("=_?\"#$%&'(),.:;<>@[\\]^`{|}~");
|
||||||
|
|
||||||
private static final String ENC_WORD_PREFIX = "=?";
|
private static final String ENC_WORD_PREFIX = "=?";
|
||||||
@ -68,7 +68,7 @@ public class EncoderUtil {
|
|||||||
if (charset == null)
|
if (charset == null)
|
||||||
charset = determineCharset(text);
|
charset = determineCharset(text);
|
||||||
|
|
||||||
String mimeCharset = MimeUtility.getExternalCharset(charset.name());
|
String mimeCharset = CharsetSupport.getExternalCharset(charset.name());
|
||||||
|
|
||||||
byte[] bytes = encode(text, charset);
|
byte[] bytes = encode(text, charset);
|
||||||
|
|
||||||
@ -130,8 +130,8 @@ public class EncoderUtil {
|
|||||||
private static int qEncodedLength(byte[] bytes) {
|
private static int qEncodedLength(byte[] bytes) {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
for (int idx = 0; idx < bytes.length; idx++) {
|
for (byte b : bytes) {
|
||||||
int v = bytes[idx] & 0xff;
|
int v = b & 0xff;
|
||||||
if (v == 32) {
|
if (v == 32) {
|
||||||
count++;
|
count++;
|
||||||
} else if (!Q_RESTRICTED_CHARS.get(v)) {
|
} else if (!Q_RESTRICTED_CHARS.get(v)) {
|
@ -0,0 +1,103 @@
|
|||||||
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.Address;
|
||||||
|
import com.fsck.k9.mail.Message;
|
||||||
|
import com.fsck.k9.mail.MessagingException;
|
||||||
|
import com.fsck.k9.mail.Part;
|
||||||
|
|
||||||
|
class JisSupport {
|
||||||
|
public static final String SHIFT_JIS = "shift_jis";
|
||||||
|
|
||||||
|
public static String getJisVariantFromMessage(Message message) throws MessagingException {
|
||||||
|
if (message == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// If a receiver is known to use a JIS variant, the sender transfers the message after converting the
|
||||||
|
// charset as a convention.
|
||||||
|
String variant = getJisVariantFromReceivedHeaders(message);
|
||||||
|
if (variant != null)
|
||||||
|
return variant;
|
||||||
|
|
||||||
|
// If a receiver is not known to use any JIS variants, the sender transfers the message without converting
|
||||||
|
// the charset.
|
||||||
|
variant = getJisVariantFromFromHeaders(message);
|
||||||
|
if (variant != null)
|
||||||
|
return variant;
|
||||||
|
|
||||||
|
return getJisVariantFromMailerHeaders(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isShiftJis(String charset) {
|
||||||
|
return charset.length() > 17 && charset.startsWith("x-")
|
||||||
|
&& charset.endsWith("-shift_jis-2007");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getJisVariantFromAddress(String address) {
|
||||||
|
if (address == null)
|
||||||
|
return null;
|
||||||
|
if (isInDomain(address, "docomo.ne.jp") || isInDomain(address, "dwmail.jp") ||
|
||||||
|
isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com") ||
|
||||||
|
isInDomain(address, "emnet.ne.jp") || isInDomain(address, "emobile.ne.jp"))
|
||||||
|
return "docomo";
|
||||||
|
else if (isInDomain(address, "softbank.ne.jp") || isInDomain(address, "vodafone.ne.jp") ||
|
||||||
|
isInDomain(address, "disney.ne.jp") || isInDomain(address, "vertuclub.ne.jp"))
|
||||||
|
return "softbank";
|
||||||
|
else if (isInDomain(address, "ezweb.ne.jp") || isInDomain(address, "ido.ne.jp"))
|
||||||
|
return "kddi";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static String getJisVariantFromMailerHeaders(Message message) throws MessagingException {
|
||||||
|
String[] mailerHeaders = message.getHeader("X-Mailer");
|
||||||
|
if (mailerHeaders.length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (mailerHeaders[0].startsWith("iPhone Mail ") || mailerHeaders[0].startsWith("iPad Mail "))
|
||||||
|
return "iphone";
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static String getJisVariantFromReceivedHeaders(Part message) throws MessagingException {
|
||||||
|
String[] receivedHeaders = message.getHeader("Received");
|
||||||
|
if (receivedHeaders.length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
for (String receivedHeader : receivedHeaders) {
|
||||||
|
String address = getAddressFromReceivedHeader(receivedHeader);
|
||||||
|
if (address == null)
|
||||||
|
continue;
|
||||||
|
String variant = getJisVariantFromAddress(address);
|
||||||
|
if (variant != null)
|
||||||
|
return variant;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getAddressFromReceivedHeader(String receivedHeader) {
|
||||||
|
// Not implemented yet! Extract an address from the FOR clause of the given Received header.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getJisVariantFromFromHeaders(Message message) throws MessagingException {
|
||||||
|
Address addresses[] = message.getFrom();
|
||||||
|
if (addresses == null || addresses.length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return getJisVariantFromAddress(addresses[0].getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isInDomain(String address, String domain) {
|
||||||
|
int index = address.length() - domain.length() - 1;
|
||||||
|
if (index < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
char c = address.charAt(index);
|
||||||
|
if (c != '@' && c != '.')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return address.endsWith(domain);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,435 @@
|
|||||||
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.Body;
|
||||||
|
import com.fsck.k9.mail.BodyPart;
|
||||||
|
import com.fsck.k9.mail.Message;
|
||||||
|
import com.fsck.k9.mail.MessagingException;
|
||||||
|
import com.fsck.k9.mail.Multipart;
|
||||||
|
import com.fsck.k9.mail.Part;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
|
||||||
|
import static com.fsck.k9.mail.internet.CharsetSupport.fixupCharset;
|
||||||
|
import static com.fsck.k9.mail.internet.MimeUtility.getHeaderParameter;
|
||||||
|
import static com.fsck.k9.mail.internet.Viewable.Alternative;
|
||||||
|
import static com.fsck.k9.mail.internet.Viewable.Html;
|
||||||
|
import static com.fsck.k9.mail.internet.Viewable.MessageHeader;
|
||||||
|
import static com.fsck.k9.mail.internet.Viewable.Text;
|
||||||
|
import static com.fsck.k9.mail.internet.Viewable.Textual;
|
||||||
|
|
||||||
|
public class MessageExtractor {
|
||||||
|
private MessageExtractor() {}
|
||||||
|
|
||||||
|
public static String getTextFromPart(Part part) {
|
||||||
|
try {
|
||||||
|
if ((part != null) && (part.getBody() != null)) {
|
||||||
|
final Body body = part.getBody();
|
||||||
|
if (body instanceof TextBody) {
|
||||||
|
return ((TextBody)body).getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String mimeType = part.getMimeType();
|
||||||
|
if ((mimeType != null) && MimeUtility.mimeTypeMatches(mimeType, "text/*")) {
|
||||||
|
/*
|
||||||
|
* We've got a text part, so let's see if it needs to be processed further.
|
||||||
|
*/
|
||||||
|
String charset = getHeaderParameter(part.getContentType(), "charset");
|
||||||
|
/*
|
||||||
|
* determine the charset from HTML message.
|
||||||
|
*/
|
||||||
|
if (mimeType.equalsIgnoreCase("text/html") && charset == null) {
|
||||||
|
InputStream in = MimeUtility.decodeBody(body);
|
||||||
|
try {
|
||||||
|
byte[] buf = new byte[256];
|
||||||
|
in.read(buf, 0, buf.length);
|
||||||
|
String str = new String(buf, "US-ASCII");
|
||||||
|
|
||||||
|
if (str.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
Pattern p = Pattern.compile("<meta http-equiv=\"?Content-Type\"? content=\"text/html; charset=(.+?)\">", Pattern.CASE_INSENSITIVE);
|
||||||
|
Matcher m = p.matcher(str);
|
||||||
|
if (m.find()) {
|
||||||
|
charset = m.group(1);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
MimeUtility.closeInputStreamWithoutDeletingTemporaryFiles(in);
|
||||||
|
} catch (IOException e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
charset = fixupCharset(charset, getMessageFromPart(part));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now we read the part into a buffer for further processing. Because
|
||||||
|
* the stream is now wrapped we'll remove any transfer encoding at this point.
|
||||||
|
*/
|
||||||
|
InputStream in = MimeUtility.decodeBody(body);
|
||||||
|
try {
|
||||||
|
return CharsetSupport.readToString(in, charset);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
MimeUtility.closeInputStreamWithoutDeletingTemporaryFiles(in);
|
||||||
|
} catch (IOException e) { /* Ignore */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (OutOfMemoryError oom) {
|
||||||
|
/*
|
||||||
|
* If we are not able to process the body there's nothing we can do about it. Return
|
||||||
|
* null and let the upper layers handle the missing content.
|
||||||
|
*/
|
||||||
|
Log.e(LOG_TAG, "Unable to getTextFromPart " + oom.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
/*
|
||||||
|
* If we are not able to process the body there's nothing we can do about it. Return
|
||||||
|
* null and let the upper layers handle the missing content.
|
||||||
|
*/
|
||||||
|
Log.e(LOG_TAG, "Unable to getTextFromPart", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverse the MIME tree of a message an extract viewable parts.
|
||||||
|
*
|
||||||
|
* @param part
|
||||||
|
* The message part to start from.
|
||||||
|
* @param attachments
|
||||||
|
* A list that will receive the parts that are considered attachments.
|
||||||
|
*
|
||||||
|
* @return A list of {@link Viewable}s.
|
||||||
|
*
|
||||||
|
* @throws MessagingException
|
||||||
|
* In case of an error.
|
||||||
|
*/
|
||||||
|
public static List<Viewable> getViewables(Part part, List<Part> attachments) throws MessagingException {
|
||||||
|
List<Viewable> viewables = new ArrayList<Viewable>();
|
||||||
|
|
||||||
|
Body body = part.getBody();
|
||||||
|
if (body instanceof Multipart) {
|
||||||
|
Multipart multipart = (Multipart) body;
|
||||||
|
if (part.getMimeType().equalsIgnoreCase("multipart/alternative")) {
|
||||||
|
/*
|
||||||
|
* For multipart/alternative parts we try to find a text/plain and a text/html
|
||||||
|
* child. Everything else we find is put into 'attachments'.
|
||||||
|
*/
|
||||||
|
List<Viewable> text = findTextPart(multipart, true);
|
||||||
|
|
||||||
|
Set<Part> knownTextParts = getParts(text);
|
||||||
|
List<Viewable> html = findHtmlPart(multipart, knownTextParts, attachments, true);
|
||||||
|
|
||||||
|
if (!text.isEmpty() || !html.isEmpty()) {
|
||||||
|
Alternative alternative = new Alternative(text, html);
|
||||||
|
viewables.add(alternative);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For all other multipart parts we recurse to grab all viewable children.
|
||||||
|
for (Part bodyPart : multipart.getBodyParts()) {
|
||||||
|
viewables.addAll(getViewables(bodyPart, attachments));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (body instanceof Message &&
|
||||||
|
!("attachment".equalsIgnoreCase(getContentDisposition(part)))) {
|
||||||
|
/*
|
||||||
|
* We only care about message/rfc822 parts whose Content-Disposition header has a value
|
||||||
|
* other than "attachment".
|
||||||
|
*/
|
||||||
|
Message message = (Message) body;
|
||||||
|
|
||||||
|
// We add the Message object so we can extract the filename later.
|
||||||
|
viewables.add(new MessageHeader(part, message));
|
||||||
|
|
||||||
|
// Recurse to grab all viewable parts and attachments from that message.
|
||||||
|
viewables.addAll(getViewables(message, attachments));
|
||||||
|
} else if (isPartTextualBody(part)) {
|
||||||
|
/*
|
||||||
|
* Save text/plain and text/html
|
||||||
|
*/
|
||||||
|
String mimeType = part.getMimeType();
|
||||||
|
if (mimeType.equalsIgnoreCase("text/plain")) {
|
||||||
|
Text text = new Text(part);
|
||||||
|
viewables.add(text);
|
||||||
|
} else {
|
||||||
|
Html html = new Html(part);
|
||||||
|
viewables.add(html);
|
||||||
|
}
|
||||||
|
} else if (part.getMimeType().equalsIgnoreCase("application/pgp-signature")) {
|
||||||
|
// ignore this type explicitly
|
||||||
|
} else {
|
||||||
|
// Everything else is treated as attachment.
|
||||||
|
attachments.add(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewables;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<Part> getTextParts(Part part) throws MessagingException {
|
||||||
|
List<Part> attachments = new ArrayList<Part>();
|
||||||
|
return getParts(getViewables(part, attachments));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect attachment parts of a message.
|
||||||
|
* @return A list of parts regarded as attachments.
|
||||||
|
* @throws MessagingException In case of an error.
|
||||||
|
*/
|
||||||
|
public static List<Part> collectAttachments(Message message) throws MessagingException {
|
||||||
|
try {
|
||||||
|
List<Part> attachments = new ArrayList<Part>();
|
||||||
|
getViewables(message, attachments);
|
||||||
|
return attachments;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MessagingException("Couldn't collect attachment parts", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect the viewable textual parts of a message.
|
||||||
|
* @return A set of viewable parts of the message.
|
||||||
|
* @throws MessagingException In case of an error.
|
||||||
|
*/
|
||||||
|
public static Set<Part> collectTextParts(Message message) throws MessagingException {
|
||||||
|
try {
|
||||||
|
return getTextParts(message);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MessagingException("Couldn't extract viewable parts", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Message getMessageFromPart(Part part) {
|
||||||
|
while (part != null) {
|
||||||
|
if (part instanceof Message)
|
||||||
|
return (Message)part;
|
||||||
|
|
||||||
|
if (!(part instanceof BodyPart))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Multipart multipart = ((BodyPart)part).getParent();
|
||||||
|
if (multipart == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
part = multipart.getParent();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search the children of a {@link Multipart} for {@code text/plain} parts.
|
||||||
|
*
|
||||||
|
* @param multipart The {@code Multipart} to search through.
|
||||||
|
* @param directChild If {@code true}, this method will return after the first {@code text/plain} was
|
||||||
|
* found.
|
||||||
|
*
|
||||||
|
* @return A list of {@link Text} viewables.
|
||||||
|
*
|
||||||
|
* @throws MessagingException
|
||||||
|
* In case of an error.
|
||||||
|
*/
|
||||||
|
private static List<Viewable> findTextPart(Multipart multipart, boolean directChild)
|
||||||
|
throws MessagingException {
|
||||||
|
List<Viewable> viewables = new ArrayList<Viewable>();
|
||||||
|
|
||||||
|
for (Part part : multipart.getBodyParts()) {
|
||||||
|
Body body = part.getBody();
|
||||||
|
if (body instanceof Multipart) {
|
||||||
|
Multipart innerMultipart = (Multipart) body;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Recurse to find text parts. Since this is a multipart that is a child of a
|
||||||
|
* multipart/alternative we don't want to stop after the first text/plain part
|
||||||
|
* we find. This will allow to get all text parts for constructions like this:
|
||||||
|
*
|
||||||
|
* 1. multipart/alternative
|
||||||
|
* 1.1. multipart/mixed
|
||||||
|
* 1.1.1. text/plain
|
||||||
|
* 1.1.2. text/plain
|
||||||
|
* 1.2. text/html
|
||||||
|
*/
|
||||||
|
List<Viewable> textViewables = findTextPart(innerMultipart, false);
|
||||||
|
|
||||||
|
if (!textViewables.isEmpty()) {
|
||||||
|
viewables.addAll(textViewables);
|
||||||
|
if (directChild) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isPartTextualBody(part) && part.getMimeType().equalsIgnoreCase("text/plain")) {
|
||||||
|
Text text = new Text(part);
|
||||||
|
viewables.add(text);
|
||||||
|
if (directChild) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return viewables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search the children of a {@link Multipart} for {@code text/html} parts.
|
||||||
|
* Every part that is not a {@code text/html} we want to display, we add to 'attachments'.
|
||||||
|
*
|
||||||
|
* @param multipart The {@code Multipart} to search through.
|
||||||
|
* @param knownTextParts A set of {@code text/plain} parts that shouldn't be added to 'attachments'.
|
||||||
|
* @param attachments A list that will receive the parts that are considered attachments.
|
||||||
|
* @param directChild If {@code true}, this method will add all {@code text/html} parts except the first
|
||||||
|
* found to 'attachments'.
|
||||||
|
*
|
||||||
|
* @return A list of {@link Text} viewables.
|
||||||
|
*
|
||||||
|
* @throws MessagingException In case of an error.
|
||||||
|
*/
|
||||||
|
private static List<Viewable> findHtmlPart(Multipart multipart, Set<Part> knownTextParts,
|
||||||
|
List<Part> attachments, boolean directChild) throws MessagingException {
|
||||||
|
List<Viewable> viewables = new ArrayList<Viewable>();
|
||||||
|
|
||||||
|
boolean partFound = false;
|
||||||
|
for (Part part : multipart.getBodyParts()) {
|
||||||
|
Body body = part.getBody();
|
||||||
|
if (body instanceof Multipart) {
|
||||||
|
Multipart innerMultipart = (Multipart) body;
|
||||||
|
|
||||||
|
if (directChild && partFound) {
|
||||||
|
// We already found our text/html part. Now we're only looking for attachments.
|
||||||
|
findAttachments(innerMultipart, knownTextParts, attachments);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Recurse to find HTML parts. Since this is a multipart that is a child of a
|
||||||
|
* multipart/alternative we don't want to stop after the first text/html part
|
||||||
|
* we find. This will allow to get all text parts for constructions like this:
|
||||||
|
*
|
||||||
|
* 1. multipart/alternative
|
||||||
|
* 1.1. text/plain
|
||||||
|
* 1.2. multipart/mixed
|
||||||
|
* 1.2.1. text/html
|
||||||
|
* 1.2.2. text/html
|
||||||
|
* 1.3. image/jpeg
|
||||||
|
*/
|
||||||
|
List<Viewable> htmlViewables = findHtmlPart(innerMultipart, knownTextParts,
|
||||||
|
attachments, false);
|
||||||
|
|
||||||
|
if (!htmlViewables.isEmpty()) {
|
||||||
|
partFound = true;
|
||||||
|
viewables.addAll(htmlViewables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!(directChild && partFound) && isPartTextualBody(part) &&
|
||||||
|
part.getMimeType().equalsIgnoreCase("text/html")) {
|
||||||
|
Html html = new Html(part);
|
||||||
|
viewables.add(html);
|
||||||
|
partFound = true;
|
||||||
|
} else if (!knownTextParts.contains(part)) {
|
||||||
|
// Only add this part as attachment if it's not a viewable text/plain part found
|
||||||
|
// earlier.
|
||||||
|
attachments.add(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverse the MIME tree and add everything that's not a known text part to 'attachments'.
|
||||||
|
*
|
||||||
|
* @param multipart
|
||||||
|
* The {@link Multipart} to start from.
|
||||||
|
* @param knownTextParts
|
||||||
|
* A set of known text parts we don't want to end up in 'attachments'.
|
||||||
|
* @param attachments
|
||||||
|
* A list that will receive the parts that are considered attachments.
|
||||||
|
*/
|
||||||
|
private static void findAttachments(Multipart multipart, Set<Part> knownTextParts,
|
||||||
|
List<Part> attachments) {
|
||||||
|
for (Part part : multipart.getBodyParts()) {
|
||||||
|
Body body = part.getBody();
|
||||||
|
if (body instanceof Multipart) {
|
||||||
|
Multipart innerMultipart = (Multipart) body;
|
||||||
|
findAttachments(innerMultipart, knownTextParts, attachments);
|
||||||
|
} else if (!knownTextParts.contains(part)) {
|
||||||
|
attachments.add(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a set of message parts for fast lookups.
|
||||||
|
*
|
||||||
|
* @param viewables
|
||||||
|
* A list of {@link Viewable}s containing references to the message parts to include in
|
||||||
|
* the set.
|
||||||
|
*
|
||||||
|
* @return The set of viewable {@code Part}s.
|
||||||
|
*
|
||||||
|
* @see MessageExtractor#findHtmlPart(Multipart, Set, List, boolean)
|
||||||
|
* @see MessageExtractor#findAttachments(Multipart, Set, List)
|
||||||
|
*/
|
||||||
|
private static Set<Part> getParts(List<Viewable> viewables) {
|
||||||
|
Set<Part> parts = new HashSet<Part>();
|
||||||
|
|
||||||
|
for (Viewable viewable : viewables) {
|
||||||
|
if (viewable instanceof Textual) {
|
||||||
|
parts.add(((Textual) viewable).getPart());
|
||||||
|
} else if (viewable instanceof Alternative) {
|
||||||
|
Alternative alternative = (Alternative) viewable;
|
||||||
|
parts.addAll(getParts(alternative.getText()));
|
||||||
|
parts.addAll(getParts(alternative.getHtml()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Boolean isPartTextualBody(Part part) throws MessagingException {
|
||||||
|
String disposition = part.getDisposition();
|
||||||
|
String dispositionType = null;
|
||||||
|
String dispositionFilename = null;
|
||||||
|
if (disposition != null) {
|
||||||
|
dispositionType = MimeUtility.getHeaderParameter(disposition, null);
|
||||||
|
dispositionFilename = MimeUtility.getHeaderParameter(disposition, "filename");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A best guess that this part is intended to be an attachment and not inline.
|
||||||
|
*/
|
||||||
|
boolean attachment = ("attachment".equalsIgnoreCase(dispositionType) || (dispositionFilename != null));
|
||||||
|
|
||||||
|
if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/html"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* If the part is plain text and it got this far it's part of a
|
||||||
|
* mixed (et al) and should be rendered inline.
|
||||||
|
*/
|
||||||
|
else if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/plain"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Finally, if it's nothing else we will include it as an attachment.
|
||||||
|
*/
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getContentDisposition(Part part) {
|
||||||
|
try {
|
||||||
|
String disposition = part.getDisposition();
|
||||||
|
if (disposition != null) {
|
||||||
|
return MimeUtility.getHeaderParameter(disposition, null);
|
||||||
|
}
|
||||||
|
} catch (MessagingException e) { /* ignore */ }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,183 @@
|
|||||||
|
|
||||||
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.Body;
|
||||||
|
import com.fsck.k9.mail.BodyPart;
|
||||||
|
import com.fsck.k9.mail.CompositeBody;
|
||||||
|
import com.fsck.k9.mail.MessagingException;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.apache.james.mime4j.util.MimeUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO this is a close approximation of Message, need to update along with
|
||||||
|
* Message.
|
||||||
|
*/
|
||||||
|
public class MimeBodyPart extends BodyPart {
|
||||||
|
private final MimeHeader mHeader = new MimeHeader();
|
||||||
|
private Body mBody;
|
||||||
|
|
||||||
|
public MimeBodyPart() throws MessagingException {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MimeBodyPart(Body body) throws MessagingException {
|
||||||
|
this(body, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MimeBodyPart(Body body, String mimeType) throws MessagingException {
|
||||||
|
if (mimeType != null) {
|
||||||
|
addHeader(MimeHeader.HEADER_CONTENT_TYPE, mimeType);
|
||||||
|
}
|
||||||
|
MimeMessageHelper.setBody(this, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFirstHeader(String name) {
|
||||||
|
return mHeader.getFirstHeader(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addHeader(String name, String value) throws MessagingException {
|
||||||
|
mHeader.addHeader(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRawHeader(String name, String raw) {
|
||||||
|
mHeader.addRawHeader(name, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeader(String name, String value) {
|
||||||
|
mHeader.setHeader(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getHeader(String name) throws MessagingException {
|
||||||
|
return mHeader.getHeader(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeHeader(String name) throws MessagingException {
|
||||||
|
mHeader.removeHeader(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Body getBody() {
|
||||||
|
return mBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBody(Body body) {
|
||||||
|
this.mBody = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEncoding(String encoding) throws MessagingException {
|
||||||
|
if (mBody != null) {
|
||||||
|
mBody.setEncoding(encoding);
|
||||||
|
}
|
||||||
|
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentType() {
|
||||||
|
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
||||||
|
return (contentType == null) ? "text/plain" : contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisposition() throws MessagingException {
|
||||||
|
return getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentId() {
|
||||||
|
String contentId = getFirstHeader(MimeHeader.HEADER_CONTENT_ID);
|
||||||
|
if (contentId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int first = contentId.indexOf('<');
|
||||||
|
int last = contentId.lastIndexOf('>');
|
||||||
|
|
||||||
|
return (first != -1 && last != -1) ?
|
||||||
|
contentId.substring(first + 1, last) :
|
||||||
|
contentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMimeType() {
|
||||||
|
return MimeUtility.getHeaderParameter(getContentType(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMimeType(String mimeType) throws MessagingException {
|
||||||
|
return getMimeType().equalsIgnoreCase(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the MimeMessage out in MIME format.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||||
|
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
||||||
|
mHeader.writeTo(out);
|
||||||
|
writer.write("\r\n");
|
||||||
|
writer.flush();
|
||||||
|
if (mBody != null) {
|
||||||
|
mBody.writeTo(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeHeaderTo(OutputStream out) throws IOException, MessagingException {
|
||||||
|
mHeader.writeTo(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUsing7bitTransport() throws MessagingException {
|
||||||
|
String type = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
||||||
|
/*
|
||||||
|
* We don't trust that a multipart/* will properly have an 8bit encoding
|
||||||
|
* header if any of its subparts are 8bit, so we automatically recurse
|
||||||
|
* (as long as its not multipart/signed).
|
||||||
|
*/
|
||||||
|
if (mBody instanceof CompositeBody
|
||||||
|
&& !"multipart/signed".equalsIgnoreCase(type)) {
|
||||||
|
setEncoding(MimeUtil.ENC_7BIT);
|
||||||
|
// recurse
|
||||||
|
((CompositeBody) mBody).setUsing7bitTransport();
|
||||||
|
} else if (!MimeUtil.ENC_8BIT
|
||||||
|
.equalsIgnoreCase(getFirstHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING))) {
|
||||||
|
return;
|
||||||
|
} else if (type != null
|
||||||
|
&& (type.equalsIgnoreCase("multipart/signed") || type
|
||||||
|
.toLowerCase(Locale.US).startsWith("message/"))) {
|
||||||
|
/*
|
||||||
|
* This shouldn't happen. In any case, it would be wrong to convert
|
||||||
|
* them to some other encoding for 7bit transport.
|
||||||
|
*
|
||||||
|
* RFC 1847 says multipart/signed must be 7bit. It also says their
|
||||||
|
* bodies must be treated as opaque, so we must not change the
|
||||||
|
* encoding.
|
||||||
|
*
|
||||||
|
* We've dealt with (CompositeBody) type message/rfc822 above. Here
|
||||||
|
* we must deal with all other message/* types. RFC 2045 says
|
||||||
|
* message/* can only be 7bit or 8bit. RFC 2046 says unknown
|
||||||
|
* message/* types must be treated as application/octet-stream,
|
||||||
|
* which means we can't recurse into them. It also says that
|
||||||
|
* existing subtypes message/partial and message/external must only
|
||||||
|
* be 7bit, and that future subtypes "should be" 7bit.
|
||||||
|
*/
|
||||||
|
throw new MessagingException(
|
||||||
|
"Unable to convert 8bit body part to 7bit");
|
||||||
|
} else {
|
||||||
|
setEncoding(MimeUtil.ENC_QUOTED_PRINTABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,203 @@
|
|||||||
|
|
||||||
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class MimeHeader {
|
||||||
|
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||||
|
|
||||||
|
public static final String HEADER_CONTENT_TYPE = "Content-Type";
|
||||||
|
public static final String HEADER_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
|
||||||
|
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
|
||||||
|
public static final String HEADER_CONTENT_ID = "Content-ID";
|
||||||
|
|
||||||
|
|
||||||
|
private List<Field> mFields = new ArrayList<Field>();
|
||||||
|
private String mCharset = null;
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
mFields.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstHeader(String name) {
|
||||||
|
String[] header = getHeader(name);
|
||||||
|
if (header.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return header[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addHeader(String name, String value) {
|
||||||
|
Field field = Field.newNameValueField(name, MimeUtility.foldAndEncode(value));
|
||||||
|
mFields.add(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addRawHeader(String name, String raw) {
|
||||||
|
Field field = Field.newRawField(name, raw);
|
||||||
|
mFields.add(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeader(String name, String value) {
|
||||||
|
if (name == null || value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeHeader(name);
|
||||||
|
addHeader(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getHeaderNames() {
|
||||||
|
Set<String> names = new LinkedHashSet<String>();
|
||||||
|
for (Field field : mFields) {
|
||||||
|
names.add(field.getName());
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getHeader(String name) {
|
||||||
|
List<String> values = new ArrayList<String>();
|
||||||
|
for (Field field : mFields) {
|
||||||
|
if (field.getName().equalsIgnoreCase(name)) {
|
||||||
|
values.add(field.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values.toArray(EMPTY_STRING_ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeHeader(String name) {
|
||||||
|
List<Field> removeFields = new ArrayList<Field>();
|
||||||
|
for (Field field : mFields) {
|
||||||
|
if (field.getName().equalsIgnoreCase(name)) {
|
||||||
|
removeFields.add(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mFields.removeAll(removeFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeTo(OutputStream out) throws IOException {
|
||||||
|
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
||||||
|
for (Field field : mFields) {
|
||||||
|
if (field.hasRawData()) {
|
||||||
|
writer.write(field.getRaw());
|
||||||
|
} else {
|
||||||
|
writeNameValueField(writer, field);
|
||||||
|
}
|
||||||
|
writer.write("\r\n");
|
||||||
|
}
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeNameValueField(BufferedWriter writer, Field field) throws IOException {
|
||||||
|
String value = field.getValue();
|
||||||
|
|
||||||
|
if (hasToBeEncoded(value)) {
|
||||||
|
Charset charset = null;
|
||||||
|
|
||||||
|
if (mCharset != null) {
|
||||||
|
charset = Charset.forName(mCharset);
|
||||||
|
}
|
||||||
|
value = EncoderUtil.encodeEncodedWord(field.getValue(), charset);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write(field.getName());
|
||||||
|
writer.write(": ");
|
||||||
|
writer.write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode non printable characters except LF/CR/TAB codes.
|
||||||
|
private boolean hasToBeEncoded(String text) {
|
||||||
|
for (int i = 0; i < text.length(); i++) {
|
||||||
|
char c = text.charAt(i);
|
||||||
|
if ((c < 0x20 || 0x7e < c) && // non printable
|
||||||
|
(c != 0x0a && c != 0x0d && c != 0x09)) { // non LF/CR/TAB
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Field {
|
||||||
|
private final String name;
|
||||||
|
private final String value;
|
||||||
|
private final String raw;
|
||||||
|
|
||||||
|
public static Field newNameValueField(String name, String value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new NullPointerException("Argument 'value' cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Field(name, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Field newRawField(String name, String raw) {
|
||||||
|
if (raw == null) {
|
||||||
|
throw new NullPointerException("Argument 'raw' cannot be null");
|
||||||
|
}
|
||||||
|
if (name != null && !raw.startsWith(name + ":")) {
|
||||||
|
throw new IllegalArgumentException("The value of 'raw' needs to start with the supplied field name " +
|
||||||
|
"followed by a colon");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Field(name, null, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Field(String name, String value, String raw) {
|
||||||
|
if (name == null) {
|
||||||
|
throw new NullPointerException("Argument 'name' cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
this.raw = raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int delimiterIndex = raw.indexOf(':');
|
||||||
|
if (delimiterIndex == raw.length() - 1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw.substring(delimiterIndex + 1).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRaw() {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasRawData() {
|
||||||
|
return raw != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return (hasRawData()) ? getRaw() : getName() + ": " + getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCharset(String charset) {
|
||||||
|
mCharset = charset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MimeHeader clone() {
|
||||||
|
MimeHeader header = new MimeHeader();
|
||||||
|
header.mCharset = mCharset;
|
||||||
|
|
||||||
|
header.mFields = new ArrayList<Field>(mFields);
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
}
|
@ -2,17 +2,20 @@
|
|||||||
package com.fsck.k9.mail.internet;
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.TimeZone;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.james.mime4j.MimeException;
|
import org.apache.james.mime4j.MimeException;
|
||||||
import org.apache.james.mime4j.dom.field.DateTimeField;
|
import org.apache.james.mime4j.dom.field.DateTimeField;
|
||||||
import org.apache.james.mime4j.field.DefaultFieldParser;
|
import org.apache.james.mime4j.field.DefaultFieldParser;
|
||||||
@ -22,22 +25,23 @@ import org.apache.james.mime4j.parser.MimeStreamParser;
|
|||||||
import org.apache.james.mime4j.stream.BodyDescriptor;
|
import org.apache.james.mime4j.stream.BodyDescriptor;
|
||||||
import org.apache.james.mime4j.stream.Field;
|
import org.apache.james.mime4j.stream.Field;
|
||||||
import org.apache.james.mime4j.stream.MimeConfig;
|
import org.apache.james.mime4j.stream.MimeConfig;
|
||||||
|
import org.apache.james.mime4j.util.MimeUtil;
|
||||||
|
|
||||||
import com.fsck.k9.mail.Address;
|
import com.fsck.k9.mail.Address;
|
||||||
import com.fsck.k9.mail.Body;
|
import com.fsck.k9.mail.Body;
|
||||||
import com.fsck.k9.mail.BodyPart;
|
import com.fsck.k9.mail.BodyPart;
|
||||||
|
import com.fsck.k9.mail.CompositeBody;
|
||||||
import com.fsck.k9.mail.Message;
|
import com.fsck.k9.mail.Message;
|
||||||
import com.fsck.k9.mail.MessagingException;
|
import com.fsck.k9.mail.MessagingException;
|
||||||
import com.fsck.k9.mail.Multipart;
|
import com.fsck.k9.mail.Multipart;
|
||||||
import com.fsck.k9.mail.Part;
|
import com.fsck.k9.mail.Part;
|
||||||
import com.fsck.k9.mail.store.UnavailableStorageException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of Message that stores all of it's metadata in RFC 822 and
|
* An implementation of Message that stores all of it's metadata in RFC 822 and
|
||||||
* RFC 2045 style headers.
|
* RFC 2045 style headers.
|
||||||
*/
|
*/
|
||||||
public class MimeMessage extends Message {
|
public class MimeMessage extends Message {
|
||||||
protected MimeHeader mHeader = new MimeHeader();
|
private MimeHeader mHeader = new MimeHeader();
|
||||||
protected Address[] mFrom;
|
protected Address[] mFrom;
|
||||||
protected Address[] mTo;
|
protected Address[] mTo;
|
||||||
protected Address[] mCc;
|
protected Address[] mCc;
|
||||||
@ -45,14 +49,15 @@ public class MimeMessage extends Message {
|
|||||||
protected Address[] mReplyTo;
|
protected Address[] mReplyTo;
|
||||||
|
|
||||||
protected String mMessageId;
|
protected String mMessageId;
|
||||||
protected String[] mReferences;
|
private String[] mReferences;
|
||||||
protected String[] mInReplyTo;
|
private String[] mInReplyTo;
|
||||||
|
|
||||||
protected Date mSentDate;
|
private Date mSentDate;
|
||||||
protected SimpleDateFormat mDateFormat;
|
private SimpleDateFormat mDateFormat;
|
||||||
|
|
||||||
protected Body mBody;
|
private Body mBody;
|
||||||
protected int mSize;
|
protected int mSize;
|
||||||
|
private String serverExtra;
|
||||||
|
|
||||||
public MimeMessage() {
|
public MimeMessage() {
|
||||||
}
|
}
|
||||||
@ -62,14 +67,23 @@ public class MimeMessage extends Message {
|
|||||||
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
|
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
|
||||||
*
|
*
|
||||||
* @param in
|
* @param in
|
||||||
|
* @param recurse A boolean indicating to recurse through all nested MimeMessage subparts.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws MessagingException
|
* @throws MessagingException
|
||||||
*/
|
*/
|
||||||
public MimeMessage(InputStream in) throws IOException, MessagingException {
|
public MimeMessage(InputStream in, boolean recurse) throws IOException, MessagingException {
|
||||||
parse(in);
|
parse(in, recurse);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void parse(InputStream in) throws IOException, MessagingException {
|
/**
|
||||||
|
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
|
||||||
|
* Does not recurse through nested bodyparts.
|
||||||
|
*/
|
||||||
|
public final void parse(InputStream in) throws IOException, MessagingException {
|
||||||
|
parse(in, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parse(InputStream in, boolean recurse) throws IOException, MessagingException {
|
||||||
mHeader.clear();
|
mHeader.clear();
|
||||||
mFrom = null;
|
mFrom = null;
|
||||||
mTo = null;
|
mTo = null;
|
||||||
@ -92,11 +106,14 @@ public class MimeMessage extends Message {
|
|||||||
parserConfig.setMaxHeaderCount(-1); // Disable the check for header count.
|
parserConfig.setMaxHeaderCount(-1); // Disable the check for header count.
|
||||||
MimeStreamParser parser = new MimeStreamParser(parserConfig);
|
MimeStreamParser parser = new MimeStreamParser(parserConfig);
|
||||||
parser.setContentHandler(new MimeMessageBuilder());
|
parser.setContentHandler(new MimeMessageBuilder());
|
||||||
|
if (recurse) {
|
||||||
|
parser.setRecurse();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
parser.parse(new EOLConvertingInputStream(in));
|
parser.parse(new EOLConvertingInputStream(in));
|
||||||
} catch (MimeException me) {
|
} catch (MimeException me) {
|
||||||
|
//TODO wouldn't a MessagingException be better?
|
||||||
throw new Error(me);
|
throw new Error(me);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,18 +139,23 @@ public class MimeMessage extends Message {
|
|||||||
* @param sentDate
|
* @param sentDate
|
||||||
* @throws com.fsck.k9.mail.MessagingException
|
* @throws com.fsck.k9.mail.MessagingException
|
||||||
*/
|
*/
|
||||||
public void addSentDate(Date sentDate) throws MessagingException {
|
public void addSentDate(Date sentDate, boolean hideTimeZone) throws MessagingException {
|
||||||
if (mDateFormat == null) {
|
if (mDateFormat == null) {
|
||||||
mDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
|
mDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hideTimeZone) {
|
||||||
|
mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
|
}
|
||||||
|
|
||||||
addHeader("Date", mDateFormat.format(sentDate));
|
addHeader("Date", mDateFormat.format(sentDate));
|
||||||
setInternalSentDate(sentDate);
|
setInternalSentDate(sentDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSentDate(Date sentDate) throws MessagingException {
|
public void setSentDate(Date sentDate, boolean hideTimeZone) throws MessagingException {
|
||||||
removeHeader("Date");
|
removeHeader("Date");
|
||||||
addSentDate(sentDate);
|
addSentDate(sentDate, hideTimeZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setInternalSentDate(Date sentDate) {
|
public void setInternalSentDate(Date sentDate) {
|
||||||
@ -141,21 +163,32 @@ public class MimeMessage extends Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getContentType() throws MessagingException {
|
public String getContentType() {
|
||||||
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
||||||
return (contentType == null) ? "text/plain" : contentType.toLowerCase(Locale.US);
|
return (contentType == null) ? "text/plain" : contentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getDisposition() throws MessagingException {
|
public String getDisposition() throws MessagingException {
|
||||||
return getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
|
return getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
|
||||||
}
|
}
|
||||||
public String getContentId() throws MessagingException {
|
|
||||||
|
@Override
|
||||||
|
public String getContentId() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
public String getMimeType() throws MessagingException {
|
|
||||||
|
@Override
|
||||||
|
public String getMimeType() {
|
||||||
return MimeUtility.getHeaderParameter(getContentType(), null);
|
return MimeUtility.getHeaderParameter(getContentType(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMimeType(String mimeType) throws MessagingException {
|
||||||
|
return getMimeType().equalsIgnoreCase(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getSize() {
|
public int getSize() {
|
||||||
return mSize;
|
return mSize;
|
||||||
}
|
}
|
||||||
@ -278,17 +311,31 @@ public class MimeMessage extends Message {
|
|||||||
if (mMessageId == null) {
|
if (mMessageId == null) {
|
||||||
mMessageId = getFirstHeader("Message-ID");
|
mMessageId = getFirstHeader("Message-ID");
|
||||||
}
|
}
|
||||||
if (mMessageId == null) { // even after checking the header
|
|
||||||
setMessageId(generateMessageId());
|
|
||||||
}
|
|
||||||
return mMessageId;
|
return mMessageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateMessageId() {
|
public void generateMessageId() throws MessagingException {
|
||||||
return "<" + UUID.randomUUID().toString() + "@email.android.com>";
|
String hostname = null;
|
||||||
|
|
||||||
|
if (mFrom != null && mFrom.length >= 1) {
|
||||||
|
hostname = mFrom[0].getHostname();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hostname == null && mReplyTo != null && mReplyTo.length >= 1) {
|
||||||
|
hostname = mReplyTo[0].getHostname();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hostname == null) {
|
||||||
|
hostname = "email.android.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We use upper case here to match Apple Mail Message-ID format (for privacy) */
|
||||||
|
String messageId = "<" + UUID.randomUUID().toString().toUpperCase(Locale.US) + "@" + hostname + ">";
|
||||||
|
|
||||||
|
setMessageId(messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMessageId(String messageId) throws UnavailableStorageException {
|
public void setMessageId(String messageId) throws MessagingException {
|
||||||
setHeader("Message-ID", messageId);
|
setHeader("Message-ID", messageId);
|
||||||
mMessageId = messageId;
|
mMessageId = messageId;
|
||||||
}
|
}
|
||||||
@ -349,49 +396,45 @@ public class MimeMessage extends Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setBody(Body body) throws MessagingException {
|
public void setBody(Body body) {
|
||||||
this.mBody = body;
|
this.mBody = body;
|
||||||
setHeader("MIME-Version", "1.0");
|
|
||||||
if (body instanceof Multipart) {
|
|
||||||
Multipart multipart = ((Multipart)body);
|
|
||||||
multipart.setParent(this);
|
|
||||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, multipart.getContentType());
|
|
||||||
} else if (body instanceof TextBody) {
|
|
||||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n charset=utf-8",
|
|
||||||
getMimeType()));
|
|
||||||
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "quoted-printable");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getFirstHeader(String name) {
|
private String getFirstHeader(String name) {
|
||||||
return mHeader.getFirstHeader(name);
|
return mHeader.getFirstHeader(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addHeader(String name, String value) throws UnavailableStorageException {
|
public void addHeader(String name, String value) throws MessagingException {
|
||||||
mHeader.addHeader(name, value);
|
mHeader.addHeader(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setHeader(String name, String value) throws UnavailableStorageException {
|
public void addRawHeader(String name, String raw) {
|
||||||
|
mHeader.addRawHeader(name, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeader(String name, String value) throws MessagingException {
|
||||||
mHeader.setHeader(name, value);
|
mHeader.setHeader(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getHeader(String name) throws UnavailableStorageException {
|
public String[] getHeader(String name) throws MessagingException {
|
||||||
return mHeader.getHeader(name);
|
return mHeader.getHeader(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeHeader(String name) throws UnavailableStorageException {
|
public void removeHeader(String name) throws MessagingException {
|
||||||
mHeader.removeHeader(name);
|
mHeader.removeHeader(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getHeaderNames() throws UnavailableStorageException {
|
public Set<String> getHeaderNames() throws MessagingException {
|
||||||
return mHeader.getHeaderNames();
|
return mHeader.getHeaderNames();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||||
|
|
||||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
||||||
@ -403,18 +446,22 @@ public class MimeMessage extends Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeHeaderTo(OutputStream out) throws IOException, MessagingException {
|
||||||
|
mHeader.writeTo(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public InputStream getInputStream() throws MessagingException {
|
public InputStream getInputStream() throws MessagingException {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEncoding(String encoding) throws UnavailableStorageException {
|
public void setEncoding(String encoding) throws MessagingException {
|
||||||
if (mBody instanceof Multipart) {
|
if (mBody != null) {
|
||||||
((Multipart)mBody).setEncoding(encoding);
|
mBody.setEncoding(encoding);
|
||||||
} else if (mBody instanceof TextBody) {
|
|
||||||
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
|
|
||||||
((TextBody)mBody).setEncoding(encoding);
|
|
||||||
}
|
}
|
||||||
|
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -423,12 +470,12 @@ public class MimeMessage extends Message {
|
|||||||
if (mBody instanceof Multipart) {
|
if (mBody instanceof Multipart) {
|
||||||
((Multipart)mBody).setCharset(charset);
|
((Multipart)mBody).setCharset(charset);
|
||||||
} else if (mBody instanceof TextBody) {
|
} else if (mBody instanceof TextBody) {
|
||||||
MimeUtility.setCharset(charset, this);
|
CharsetSupport.setCharset(charset, this);
|
||||||
((TextBody)mBody).setCharset(charset);
|
((TextBody)mBody).setCharset(charset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MimeMessageBuilder implements ContentHandler {
|
private class MimeMessageBuilder implements ContentHandler {
|
||||||
private final LinkedList<Object> stack = new LinkedList<Object>();
|
private final LinkedList<Object> stack = new LinkedList<Object>();
|
||||||
|
|
||||||
public MimeMessageBuilder() {
|
public MimeMessageBuilder() {
|
||||||
@ -441,43 +488,46 @@ public class MimeMessage extends Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void startMessage() {
|
public void startMessage() {
|
||||||
if (stack.isEmpty()) {
|
if (stack.isEmpty()) {
|
||||||
stack.addFirst(MimeMessage.this);
|
stack.addFirst(MimeMessage.this);
|
||||||
} else {
|
} else {
|
||||||
expect(Part.class);
|
expect(Part.class);
|
||||||
try {
|
Part part = (Part) stack.peek();
|
||||||
MimeMessage m = new MimeMessage();
|
|
||||||
((Part)stack.peek()).setBody(m);
|
MimeMessage m = new MimeMessage();
|
||||||
stack.addFirst(m);
|
part.setBody(m);
|
||||||
} catch (MessagingException me) {
|
stack.addFirst(m);
|
||||||
throw new Error(me);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void endMessage() {
|
public void endMessage() {
|
||||||
expect(MimeMessage.class);
|
expect(MimeMessage.class);
|
||||||
stack.removeFirst();
|
stack.removeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void startHeader() {
|
public void startHeader() {
|
||||||
expect(Part.class);
|
expect(Part.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
|
||||||
|
|
||||||
public void endHeader() {
|
public void endHeader() {
|
||||||
expect(Part.class);
|
expect(Part.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void startMultipart(BodyDescriptor bd) {
|
public void startMultipart(BodyDescriptor bd) {
|
||||||
expect(Part.class);
|
expect(Part.class);
|
||||||
|
|
||||||
Part e = (Part)stack.peek();
|
Part e = (Part)stack.peek();
|
||||||
try {
|
try {
|
||||||
MimeMultipart multiPart = new MimeMultipart(e.getContentType());
|
String contentType = e.getContentType();
|
||||||
|
String mimeType = MimeUtility.getHeaderParameter(contentType, null);
|
||||||
|
String boundary = MimeUtility.getHeaderParameter(contentType, "boundary");
|
||||||
|
MimeMultipart multiPart = new MimeMultipart(mimeType, boundary);
|
||||||
e.setBody(multiPart);
|
e.setBody(multiPart);
|
||||||
stack.addFirst(multiPart);
|
stack.addFirst(multiPart);
|
||||||
} catch (MessagingException me) {
|
} catch (MessagingException me) {
|
||||||
@ -485,20 +535,37 @@ public class MimeMessage extends Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void body(BodyDescriptor bd, InputStream in) throws IOException {
|
public void body(BodyDescriptor bd, InputStream in) throws IOException {
|
||||||
expect(Part.class);
|
expect(Part.class);
|
||||||
Body body = MimeUtility.decodeBody(in, bd.getTransferEncoding());
|
|
||||||
try {
|
try {
|
||||||
|
Body body = MimeUtility.createBody(in, bd.getTransferEncoding(), bd.getMimeType());
|
||||||
((Part)stack.peek()).setBody(body);
|
((Part)stack.peek()).setBody(body);
|
||||||
} catch (MessagingException me) {
|
} catch (MessagingException me) {
|
||||||
throw new Error(me);
|
throw new Error(me);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void endMultipart() {
|
public void endMultipart() {
|
||||||
stack.removeFirst();
|
expect(Multipart.class);
|
||||||
|
Multipart multipart = (Multipart) stack.removeFirst();
|
||||||
|
|
||||||
|
boolean hasNoBodyParts = multipart.getCount() == 0;
|
||||||
|
boolean hasNoEpilogue = multipart.getEpilogue() == null;
|
||||||
|
if (hasNoBodyParts && hasNoEpilogue) {
|
||||||
|
/*
|
||||||
|
* The parser is calling startMultipart(), preamble(), and endMultipart() when all we have is
|
||||||
|
* headers of a "multipart/*" part. But there's really no point in keeping a Multipart body if all
|
||||||
|
* of the content is missing.
|
||||||
|
*/
|
||||||
|
expect(Part.class);
|
||||||
|
Part part = (Part) stack.peek();
|
||||||
|
part.setBody(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void startBodyPart() {
|
public void startBodyPart() {
|
||||||
expect(MimeMultipart.class);
|
expect(MimeMultipart.class);
|
||||||
|
|
||||||
@ -511,32 +578,29 @@ public class MimeMessage extends Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void endBodyPart() {
|
public void endBodyPart() {
|
||||||
expect(BodyPart.class);
|
expect(BodyPart.class);
|
||||||
stack.removeFirst();
|
stack.removeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void epilogue(InputStream is) throws IOException {
|
@Override
|
||||||
expect(MimeMultipart.class);
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
int b;
|
|
||||||
while ((b = is.read()) != -1) {
|
|
||||||
sb.append((char)b);
|
|
||||||
}
|
|
||||||
// ((Multipart) stack.peek()).setEpilogue(sb.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void preamble(InputStream is) throws IOException {
|
public void preamble(InputStream is) throws IOException {
|
||||||
expect(MimeMultipart.class);
|
expect(MimeMultipart.class);
|
||||||
StringBuilder sb = new StringBuilder();
|
ByteArrayOutputStream preamble = new ByteArrayOutputStream();
|
||||||
int b;
|
IOUtils.copy(is, preamble);
|
||||||
while ((b = is.read()) != -1) {
|
((MimeMultipart)stack.peek()).setPreamble(preamble.toByteArray());
|
||||||
sb.append((char)b);
|
|
||||||
}
|
|
||||||
((MimeMultipart)stack.peek()).setPreamble(sb.toString());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void epilogue(InputStream is) throws IOException {
|
||||||
|
expect(MimeMultipart.class);
|
||||||
|
ByteArrayOutputStream epilogue = new ByteArrayOutputStream();
|
||||||
|
IOUtils.copy(is, epilogue);
|
||||||
|
((MimeMultipart) stack.peek()).setEpilogue(epilogue.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void raw(InputStream is) throws IOException {
|
public void raw(InputStream is) throws IOException {
|
||||||
throw new UnsupportedOperationException("Not supported");
|
throw new UnsupportedOperationException("Not supported");
|
||||||
}
|
}
|
||||||
@ -545,7 +609,9 @@ public class MimeMessage extends Message {
|
|||||||
public void field(Field parsedField) throws MimeException {
|
public void field(Field parsedField) throws MimeException {
|
||||||
expect(Part.class);
|
expect(Part.class);
|
||||||
try {
|
try {
|
||||||
((Part)stack.peek()).addHeader(parsedField.getName(), parsedField.getBody().trim());
|
String name = parsedField.getName();
|
||||||
|
String raw = parsedField.getRaw().toString();
|
||||||
|
((Part) stack.peek()).addRawHeader(name, raw);
|
||||||
} catch (MessagingException me) {
|
} catch (MessagingException me) {
|
||||||
throw new Error(me);
|
throw new Error(me);
|
||||||
}
|
}
|
||||||
@ -555,28 +621,27 @@ public class MimeMessage extends Message {
|
|||||||
/**
|
/**
|
||||||
* Copy the contents of this object into another {@code MimeMessage} object.
|
* Copy the contents of this object into another {@code MimeMessage} object.
|
||||||
*
|
*
|
||||||
* @param message
|
* @param destination The {@code MimeMessage} object to receive the contents of this instance.
|
||||||
* The {@code MimeMessage} object to receive the contents of this instance.
|
|
||||||
*/
|
*/
|
||||||
protected void copy(MimeMessage message) {
|
protected void copy(MimeMessage destination) {
|
||||||
super.copy(message);
|
super.copy(destination);
|
||||||
|
|
||||||
message.mHeader = mHeader.clone();
|
destination.mHeader = mHeader.clone();
|
||||||
|
|
||||||
message.mBody = mBody;
|
destination.mBody = mBody;
|
||||||
message.mMessageId = mMessageId;
|
destination.mMessageId = mMessageId;
|
||||||
message.mSentDate = mSentDate;
|
destination.mSentDate = mSentDate;
|
||||||
message.mDateFormat = mDateFormat;
|
destination.mDateFormat = mDateFormat;
|
||||||
message.mSize = mSize;
|
destination.mSize = mSize;
|
||||||
|
|
||||||
// These arrays are not supposed to be modified, so it's okay to reuse the references
|
// These arrays are not supposed to be modified, so it's okay to reuse the references
|
||||||
message.mFrom = mFrom;
|
destination.mFrom = mFrom;
|
||||||
message.mTo = mTo;
|
destination.mTo = mTo;
|
||||||
message.mCc = mCc;
|
destination.mCc = mCc;
|
||||||
message.mBcc = mBcc;
|
destination.mBcc = mBcc;
|
||||||
message.mReplyTo = mReplyTo;
|
destination.mReplyTo = mReplyTo;
|
||||||
message.mReferences = mReferences;
|
destination.mReferences = mReferences;
|
||||||
message.mInReplyTo = mInReplyTo;
|
destination.mInReplyTo = mInReplyTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -586,15 +651,73 @@ public class MimeMessage extends Message {
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public long getId() {
|
public long getId() {
|
||||||
return Long.parseLong(mUid); //or maybe .mMessageId?
|
return Long.parseLong(mUid); //or maybe .mMessageId?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getPreview() {
|
public String getPreview() {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean hasAttachments() {
|
public boolean hasAttachments() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUsing7bitTransport() throws MessagingException {
|
||||||
|
String type = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
||||||
|
/*
|
||||||
|
* We don't trust that a multipart/* will properly have an 8bit encoding
|
||||||
|
* header if any of its subparts are 8bit, so we automatically recurse
|
||||||
|
* (as long as its not multipart/signed).
|
||||||
|
*/
|
||||||
|
if (mBody instanceof CompositeBody
|
||||||
|
&& !"multipart/signed".equalsIgnoreCase(type)) {
|
||||||
|
setEncoding(MimeUtil.ENC_7BIT);
|
||||||
|
// recurse
|
||||||
|
((CompositeBody) mBody).setUsing7bitTransport();
|
||||||
|
} else if (!MimeUtil.ENC_8BIT
|
||||||
|
.equalsIgnoreCase(getFirstHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING))) {
|
||||||
|
return;
|
||||||
|
} else if (type != null
|
||||||
|
&& (type.equalsIgnoreCase("multipart/signed") || type
|
||||||
|
.toLowerCase(Locale.US).startsWith("message/"))) {
|
||||||
|
/*
|
||||||
|
* This shouldn't happen. In any case, it would be wrong to convert
|
||||||
|
* them to some other encoding for 7bit transport.
|
||||||
|
*
|
||||||
|
* RFC 1847 says multipart/signed must be 7bit. It also says their
|
||||||
|
* bodies must be treated as opaque, so we must not change the
|
||||||
|
* encoding.
|
||||||
|
*
|
||||||
|
* We've dealt with (CompositeBody) type message/rfc822 above. Here
|
||||||
|
* we must deal with all other message/* types. RFC 2045 says
|
||||||
|
* message/* can only be 7bit or 8bit. RFC 2046 says unknown
|
||||||
|
* message/* types must be treated as application/octet-stream,
|
||||||
|
* which means we can't recurse into them. It also says that
|
||||||
|
* existing subtypes message/partial and message/external must only
|
||||||
|
* be 7bit, and that future subtypes "should be" 7bit.
|
||||||
|
*/
|
||||||
|
throw new MessagingException(
|
||||||
|
"Unable to convert 8bit body part to 7bit");
|
||||||
|
} else {
|
||||||
|
setEncoding(MimeUtil.ENC_QUOTED_PRINTABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServerExtra() {
|
||||||
|
return serverExtra;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setServerExtra(String serverExtra) {
|
||||||
|
this.serverExtra = serverExtra;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.Body;
|
||||||
|
import com.fsck.k9.mail.Message;
|
||||||
|
import com.fsck.k9.mail.MessagingException;
|
||||||
|
import com.fsck.k9.mail.Multipart;
|
||||||
|
import com.fsck.k9.mail.Part;
|
||||||
|
import org.apache.james.mime4j.util.MimeUtil;
|
||||||
|
|
||||||
|
|
||||||
|
public class MimeMessageHelper {
|
||||||
|
private MimeMessageHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setBody(Part part, Body body) throws MessagingException {
|
||||||
|
part.setBody(body);
|
||||||
|
|
||||||
|
if (part instanceof Message) {
|
||||||
|
part.setHeader("MIME-Version", "1.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body instanceof Multipart) {
|
||||||
|
Multipart multipart = ((Multipart) body);
|
||||||
|
multipart.setParent(part);
|
||||||
|
String mimeType = multipart.getMimeType();
|
||||||
|
String contentType = String.format("%s; boundary=\"%s\"", mimeType, multipart.getBoundary());
|
||||||
|
part.setHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType);
|
||||||
|
if ("multipart/signed".equalsIgnoreCase(mimeType)) {
|
||||||
|
setEncoding(part, MimeUtil.ENC_7BIT);
|
||||||
|
} else {
|
||||||
|
setEncoding(part, MimeUtil.ENC_8BIT);
|
||||||
|
}
|
||||||
|
} else if (body instanceof TextBody) {
|
||||||
|
String contentType = String.format("%s;\r\n charset=utf-8", part.getMimeType());
|
||||||
|
String name = MimeUtility.getHeaderParameter(part.getContentType(), "name");
|
||||||
|
if (name != null) {
|
||||||
|
contentType += String.format(";\r\n name=\"%s\"", name);
|
||||||
|
}
|
||||||
|
part.setHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType);
|
||||||
|
|
||||||
|
setEncoding(part, MimeUtil.ENC_8BIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setEncoding(Part part, String encoding) throws MessagingException {
|
||||||
|
Body body = part.getBody();
|
||||||
|
if (body != null) {
|
||||||
|
body.setEncoding(encoding);
|
||||||
|
}
|
||||||
|
part.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
|
||||||
|
}
|
||||||
|
}
|