Compare commits

...

799 Commits

Author SHA1 Message Date
Travis Burtrum 54f9fd36a7 Set SNI hostname if we can for TLS connections 2015-07-24 23:52:14 -04:00
Jan Berkel 8de2ec7f27 Merge pull request #719 from dougsparling/master
Don't use StringReader in HtmlConverter as calls to read have unneces…
2015-07-24 11:23:31 +01:00
Doug Sparling ecd5239c2b Don't use StringReader in HtmlConverter as calls to read have unnecessary locking. 2015-07-23 00:07:21 -05:00
cketti 9050ef16a2 Merge pull request #686 from k9mail/art/more-static-analysis-fixes
Fix static analysis warnings and stop using nulls everywhere
2015-07-16 08:39:34 +02:00
cketti fde7b985fc Merge pull request #700 from k9mail/issue-661_disable_notifications_during_quiet_time
Add setting to completely disable notifications during Quiet Time
2015-07-06 22:23:45 +02:00
cketti 53c87ff974 Merge branch 'GH-616_keep_delete_policy_when_editing_server_settings' 2015-07-06 21:07:39 +02:00
cketti d8aef84127 Don't overwrite delete policy when editing incoming server settings
Fixes #616
2015-07-06 20:40:10 +02:00
cketti 2f7afa8288 Merge branch 'GH-706_fix_delete_uri' 2015-07-06 20:06:43 +02:00
cketti ecb6893e6c Properly encode folder name in MessageProvider delete URI
Fixes #706
2015-07-06 20:02:19 +02:00
Art O Cathain e935feb068 extract variable per review comment 2015-07-01 16:59:57 +01:00
Art O Cathain 32cc97207c do not throw exception in MessagingController per review comment 2015-07-01 16:57:13 +01:00
cketti 8494d9942d Merge branch 'readme' 2015-06-28 07:29:34 +02:00
cketti 67d2db7c85 Update README 2015-06-28 07:28:40 +02:00
cketti 54d30833fc Merge pull request #694 from k9mail/send_error_handling
Improve send failure handling
2015-06-28 05:54:43 +02:00
cketti d08942cc50 Add 'notificationDuringQuietTimeEnabled' to settings import/export 2015-06-28 05:24:30 +02:00
cketti 7959033f26 Change naming/wording 2015-06-28 05:14:21 +02:00
Sander Baas 07c12e83d0 Add option to disable notifications in quiet time
Fixes #661
2015-06-28 05:14:21 +02:00
cketti a489cc3771 Merge pull request #692 from bashrc/bashrc/linux-build-script
Build script for Debian
2015-06-28 04:14:28 +02:00
cketti d301d63274 Add support for saving message/rfc822 parts
Fixes #603
2015-06-26 06:15:32 +02:00
Bob Mottram 17a2309bb9 Use https to download SDK 2015-06-24 09:08:40 +01:00
cketti 9d44f0e062 Improve send failure handling
We now no longer parse the exception message in MessagingController to find
out if it was a permanent SMTP failure.
Overall the code is still a mess, but the error handling should be a little
bit better and more readable now.
2015-06-22 00:43:31 +02:00
Bob Mottram d16d95d840 Use standalone sdk
This makes the build script a bit more independent
from Debian specifically
2015-06-21 18:21:48 +01:00
Chris Rhodes 777ace3420 Created README
Copied from the google code page.
2015-06-19 23:02:56 +10:00
Bob Mottram 05f4002a2a
Build script for Debian
Add a script which builds the project on Debian
and produces an apk. This makes creating test builds
a little easier
2015-06-19 12:13:55 +01:00
cketti ee7a95b750 Merge pull request #685 from k9mail/art/static-analysis-fixes
static analysis fixes
2015-06-17 21:51:11 +02:00
cketti 366531bdf7 Fix code style 2015-06-17 21:11:24 +02:00
Art O Cathain 7fc8767a5c fix static analysis warnings and stop using nulls everywhere 2015-06-13 16:47:35 +01:00
Art O Cathain 591785a3ab fix up some dodgy nulls 2015-06-13 16:24:58 +01:00
Art O Cathain 2d45e53739 fix potential NPE 2015-06-13 15:29:58 +01:00
cketti e01b1b189f Merge pull request #653 from k9mail/change_folder_sync
Don't write messages without (partial) body to database
2015-06-10 02:29:50 +02:00
cketti 2fdf076d4e Merge pull request #669 from vt0r/disable_sslv3_and_rc4
Disabling support for SSLv3 protocol/ciphers and all RC4 ciphers.
2015-06-10 01:41:23 +02:00
Jan Berkel 3c1c1e4e58 Remove unneeded static modifier 2015-06-06 12:11:20 +01:00
Jan Berkel 270d22681f Merge pull request #652 from k9mail/gradle_unit_test_support
Gradle unit test support
2015-06-03 10:19:45 +01:00
Salvatore LaMendola f0962fdb6a Create a protocols blacklist that should work in the same way as the ciphers one does. 2015-06-01 17:55:59 -04:00
Salvatore LaMendola 37a313efb5 Disabling support for SSLv3 protocol/ciphers and all RC4 ciphers. 2015-05-29 12:57:23 -04:00
cketti 586fc314a1 Merge pull request #657
Add support for GCM cipher suites
2015-05-25 18:33:16 +02:00
cketti 4fb12ff12b Fix cipher suite names 2015-05-25 18:24:00 +02:00
brian m. carlson a63a91fa54 Support GCM cipher suites. 2015-05-23 21:32:22 +00:00
cketti 6138afb579 Don't write messages without (partial) body to database
Opening such messages during download will display "No text" and (probably
due to a bug) might lead to the synchronization process being aborted. Instead
of fixing the UI issue we now don't write these incomplete messages to the
database. This has the potential to massively speed up the sync process. But
it will take longer for messages to show up in the message list, especially
with slow connections.
2015-05-22 07:25:17 +02:00
cketti 1cd7df1369 Don't write the new push state to the database for every message 2015-05-22 07:25:14 +02:00
cketti 706fd85a04 Remove tests-on-jvm module 2015-05-21 14:53:34 +02:00
cketti 916929e507 Fix getting code coverage on CI builds 2015-05-21 14:52:50 +02:00
cketti 9c258ee60c Ignore Lint warning for OldTargetApi 2015-05-21 02:21:39 +02:00
cketti a24d85d754 Move library tests to k9mail-library module 2015-05-21 02:21:38 +02:00
cketti 4b273c1749 Move some tests from 'androidTest' to 'test' folder 2015-05-20 21:00:43 +02:00
cketti 0f66cacf10 Move JVM tests into main k9mail module
Use Robolectric for the tests that use framework classes.
2015-05-20 21:00:43 +02:00
cketti b45065a5b2 Improve FindBugs Gradle task
Based on https://github.com/square/sqlbrite/blob/master/gradle/android-findbugs.gradle
2015-05-20 21:00:43 +02:00
cketti f96ffdcd4e Update Gradle Android plugin to 1.2.3 2015-05-20 18:04:20 +02:00
cketti b660d45b6c Merge branch '5.103_with_bugfixes' 2015-05-02 18:11:40 +02:00
cketti 9d225dc84c Bump version to 5.106 2015-05-02 17:57:09 +02:00
cketti 0b49c85c93 Prepare changelog for 5.106 2015-05-02 17:56:36 +02:00
cketti 4784a3f6ff Do less work in RigidWebView in 'no throttle' case 2015-05-02 17:55:15 +02:00
Samuel Sieb c4723cba4a Disable RigidWebView resize throttling on Android Lollipop or higher.
This fixes the problem with blank messages.
2015-05-02 17:55:12 +02:00
cketti ce86e773e0 Use Greenmail 1.4.1 release instead of (now removed) snapshot 2015-04-30 10:10:54 +02:00
cketti 3e833580ac Use numbered parameters in format string
… because Transifex is stupid and complains when a parameter doesn't show
up in a translation exactly like it's defined in the source language.
2015-04-30 09:58:31 +02:00
Marcus Wolschon 05934d75d8 wrong parameter order in format string.
(Found via Android Studio Lint tool)
2015-04-28 23:08:17 +02:00
Marcus Wolschon 64e22a72ed Some first android wear support for enhancement
#619  "Add android wear support"

No reply with voice yet (as requested in the ticket).
No user-configurable actions yet, just delete+archive+spam
No stacked notification for multiple messages yet.
2015-04-28 22:26:17 +02:00
Marcus Wolschon 0f848ee51f reverting accidental commit 1dfc2a5490 2015-04-28 22:17:02 +02:00
Marcus Wolschon 1dfc2a5490 Merge remote-tracking branch 'origin/master' 2015-04-28 22:05:37 +02:00
Marcus Wolschon c37934ea16 Fixed wrong parameter order leading to broken MessageReferences. 2015-04-28 22:04:22 +02:00
cketti d538278be6 Don't write HtmlConverterTest results to a file
Fixes issue #618
2015-04-28 05:43:35 +02:00
cketti 83bb97b0c5 Revert "Potentially avoid creating new Typeface instances"
This reverts commit 9df1a3ee80.
2015-04-04 01:46:53 +02:00
cketti 017ae1d2f3 Merge pull request #590
WebView: Open links in external Browser
2015-04-04 01:20:08 +02:00
cketti babd3a530f Add activity flags to browser view intent 2015-04-04 01:06:14 +02:00
cketti ba8cb6c85d Set EXTRA_CREATE_NEW_TAB to 'true' in browser view intent 2015-04-04 00:57:47 +02:00
cketti 09babb6e88 Restructure the code a bit 2015-04-04 00:55:53 +02:00
cketti 8bcf9b1d50 Remove comments 2015-04-04 00:50:14 +02:00
cketti 80fa468ec2 Merge pull request #581
MessageReference class refactor
2015-04-03 21:36:26 +02:00
cketti 24b61e0743 Fix code formatting 2015-04-03 21:33:38 +02:00
cketti a3375d7030 Make MessageReference fields private 2015-04-03 20:43:49 +02:00
cketti 9275bb2943 Remove unused constructor 2015-04-03 20:35:33 +02:00
cketti af36129449 Extract local variables for easier readability 2015-04-03 20:27:11 +02:00
cketti 632517be81 Remove trivial comments 2015-04-03 19:50:50 +02:00
cketti 79b06cd0cc Merge pull request #601 from artbristol/art/fix-race-alternative
Remove references to Account instance when account is deleted
2015-03-31 21:33:47 +02:00
Art O Cathain 1ec2c5b095 Clarify 2015-03-31 20:29:51 +01:00
Art O Cathain 6a03e62f52 ensure account also removed from memories on delete 2015-03-31 20:10:25 +01:00
m0viefreak 46f74bd11c WebView: Open links in external Browser
1a20ca06f1 connected a WebViewClient
to the WebView. But as soon as a client is connected, the WebView
stops handling links itself and tries to display everything on
its own.

Override shouldOverrideUrlLoading() and replicate what Android's
default WebView does if no WebViewClient is connected to work
around this.

This fixes #587.
2015-03-26 02:27:10 +01:00
Dominik Schürmann ffc5ba2cf3 PGP: Set correct encryption state in message crypto annotation via RESULT_TYPE 2015-03-21 15:40:15 +01:00
Valentin CAULIER ebcd10d1b1 Merge remote-tracking branch 'upstream/master' into MessageReferenceImmutability 2015-03-21 11:53:40 +01:00
Valentin CAULIER 13f6b42250 MessageReference is now immutable 2015-03-21 11:12:16 +01:00
Valentin CAULIER 3e84c20c9b Updating other classes to use new constructor and getters of
MessageReference
2015-03-21 11:12:13 +01:00
Dominik Schürmann 16f09611fe PGP: Introduce key preference per account for OpenPGP APIv7 2015-03-20 15:14:48 +01:00
Dominik Schürmann dc95f5feab Remove test again (slipped in) 2015-03-20 14:35:47 +01:00
Dominik Schürmann 05574b0f16 Update OpenPGP API lib 2015-03-20 14:33:29 +01:00
cketti 3762a7bc69 Add minSdkVersion to openpgp-api-library to fix build failure 2015-03-16 18:07:58 +01:00
Valentin CAULIER bf0333ba31 Beginning MessageReference update to immutable object 2015-03-16 16:29:42 +01:00
cketti ab964cf8af Merge branch 'pgp_mime_preparations' 2015-03-16 16:14:45 +01:00
cketti 54831fba73 Remove test for openpgp-api-library code 2015-03-16 16:05:25 +01:00
Dominik Schürmann e4cfd3c886 Update openpgp-api-library for APIv7 2015-03-16 14:29:12 +01:00
cketti f0e1b14b58 Merge pull request #567
Move NetworkType to k9mail-library, use on StoreConfig
2015-03-16 13:39:59 +01:00
cketti 494b16196d Rename AccountCreator.calculateDefaultDeletePolicy() 2015-03-16 13:28:11 +01:00
cketti 5f14e3b4e1 Use switch statement inside calculateDefaultDeletePolicy()
With this - at least in theory  - the JIT compiler can produce better code
than is possible with the static HashMap.
2015-03-16 13:26:44 +01:00
cketti f733cc38ba Merge pull request #566
Rationalize default ports
2015-03-16 13:15:43 +01:00
cketti 00528f5d24 Move throw statement outside of switch body
This way static analysis can detect when we're missing a switch case.
2015-03-16 13:11:01 +01:00
cketti 855da35f3a Code style fixes 2015-03-16 13:06:40 +01:00
cketti d528864a25 Merge branch '5.103_with_bugfixes'
Make sure 5.105 can be found in master branch
2015-03-14 12:28:20 +01:00
cketti 83de921a1a Bump version to 5.105 2015-03-14 12:10:13 +01:00
cketti c147ce0822 Prepare changelog for 5.105 2015-03-14 12:06:50 +01:00
cketti 672a85bcf4 Fix crashes when selecting messages in the message list
Throughout the code we make the assumption that onPrepareActionMode() is
called right after starting the action mode. However, this is not the case on
Android 5.1.
With this change we call ActionMode.invalidate() right after starting the
action mode which causes onPrepareActionMode() to be invoked.
2015-03-14 11:58:05 +01:00
cketti e1bd260bd0 Copy changelog for 5.104 2015-03-14 11:57:53 +01:00
cketti 08beb212c2 Fix crashes when selecting messages in the message list
Throughout the code we make the assumption that onPrepareActionMode() is
called right after starting the action mode. However, this is not the case on
Android 5.1.
With this change we call ActionMode.invalidate() right after starting the
action mode which causes onPrepareActionMode() to be invoked.
2015-03-13 21:12:21 +01:00
cketti 42419fc4e5 Bump version to 5.104 2015-03-13 13:10:11 +01:00
cketti 20a1fa384c Prepare changelog for 5.104 2015-03-13 12:53:17 +01:00
cketti 9f2bbe9ae4 Add Serbian translation 2015-03-13 12:48:50 +01:00
cketti e0a249098b Update translations 2015-03-13 12:42:16 +01:00
cketti 8e0a30c5f2 Fix Transifex fixup script 2015-03-13 12:40:56 +01:00
Art O Cathain 110400a85b move NetworkType to k9mail-library, use on StoreConfig 2015-03-08 12:59:15 +00:00
Art O Cathain 520e327775 remove duplication 2015-03-08 11:26:54 +00:00
Art O Cathain d0cd7c368d formatting 2015-03-08 11:07:33 +00:00
Art O Cathain 810d0cf6b4 Move default port and default TLS port to a single location 2015-03-08 11:05:53 +00:00
cketti 147db8cc5e Configure 'developer mode' via BuildConfig 2015-03-07 00:30:50 +01:00
cketti 2f832e5fa4 Merge pull request #562 from artbristol/art/tidy-check-settings-async-task
Refactor to make code more readable
2015-03-06 23:52:22 +01:00
cketti 46bac187d5 Merge pull request #557 from artbristol/art/account-creation-refactor-2
Remove duplication, enum-ify String
2015-03-06 23:47:46 +01:00
cketti 67404b1883 Merge pull request #553
Remove redundant variable assignments
Minimize scope of local variables
2015-03-06 23:13:54 +01:00
cketti abd2b20850 Minimize scope of local variables 2015-03-06 23:12:28 +01:00
cketti 29f7552c3a Merge pull request #548
Rename Searchfield to SearchField
2015-03-06 23:00:18 +01:00
cketti b826d4e98d Use imports for inner classes 2015-03-06 21:19:27 +01:00
cketti bf344dee5d Merge branch 'master' into pgp_mime_preparations 2015-03-03 00:33:45 +01:00
cketti 2a404b30d2 Small refactoring of MessageBuilder 2015-03-03 00:26:43 +01:00
cketti cb0a99281a Refactor IdentityHeaderBuilder to make it a bit more readable 2015-03-03 00:11:59 +01:00
cketti bc284584d1 Move code to build messages outside of MessageCompose 2015-03-02 23:52:35 +01:00
cketti 5330fe5b27 Extract code to parse a message header to its own class 2015-02-25 03:36:48 +01:00
Art O Cathain d0fa82269f review comments 2015-02-23 17:28:42 +00:00
cketti dd20ff5aa3 Merge pull request #561
Reduce code duplication in AccountSetupAccountType
2015-02-23 03:39:07 +01:00
cketti 737e0d2ac8 Minor code style fixes 2015-02-23 03:36:34 +01:00
cketti 4db7de4ed8 Merge pull request #559 2015-02-23 03:13:28 +01:00
cketti d443a6d4eb Add more tests 2015-02-23 02:58:16 +01:00
cketti a979accb54 Add helper methods to get more readable tests 2015-02-23 02:58:16 +01:00
cketti af491fdb41 Remove comments 2015-02-23 02:23:00 +01:00
cketti 084e7a1687 Change names of test methods 2015-02-23 02:22:10 +01:00
cketti 8fbb3edd1c Fix code style 2015-02-23 01:45:30 +01:00
Art O Cathain 5035e7e3d1 review comments, also use == in preference to equals for enums 2015-02-22 21:30:26 +00:00
Valentin CAULIER 23c01b0390 MessageReference class, method equals() unit tests 2015-02-22 21:11:13 +01:00
Art O Cathain a656a61c65 tidy method 2015-02-22 17:01:14 +00:00
Art O Cathain d5d42469b0 Avoid confusing reuse of local variables 2015-02-22 16:03:58 +00:00
Art O Cathain 492d65feed reduce duplication 2015-02-22 15:52:16 +00:00
cketti e7f706b78d Show new decrypt/verify error conditions in crypto header 2015-02-21 04:31:44 +01:00
cketti bcb668300f Refactor OpenPgpHeaderView for readability 2015-02-21 03:24:41 +01:00
cketti 6def0be158 Add helper methods to shorten code 2015-02-21 02:57:02 +01:00
cketti fd99c279e5 Remove "m" prefix of field names 2015-02-21 02:52:55 +01:00
cketti ab877453d9 Use enum instead of int constants 2015-02-21 02:50:47 +01:00
cketti d61ac959a9 Limit method/field accessibility 2015-02-21 02:39:34 +01:00
cketti ae258f5761 Remove unused method 2015-02-21 02:39:34 +01:00
cketti b40749547c Don't show parts we can't (yet) decrypt as attachments 2015-02-21 02:39:34 +01:00
cketti e5e4c29736 Save type of crypto part for later use 2015-02-21 02:39:34 +01:00
cketti 6f3f555986 Add support for new decrypt/verify error conditions
We can decrypt or verify a message if it was only partly downloaded.
2015-02-21 02:39:33 +01:00
cketti c6abb50d10 Refactor code to get smaller methods 2015-02-20 18:31:42 +01:00
Art O Cathain 1a706c3113 formatting 2015-02-18 19:48:17 +00:00
Art O Cathain 583a637d79 Remove duplication, enum-ify String 2015-02-18 19:42:33 +00:00
cketti d8448c3510 Only make http or https URIs trigger the "Show pictures" button 2015-02-17 23:42:26 +01:00
cketti ab8746ffe9 Hide side bar when not showing the crypto header view 2015-02-17 23:18:46 +01:00
cketti 132ede425b Make it easier to check if a crypto provider is configured 2015-02-17 20:17:34 +01:00
cketti cac1f1ca0d Do MessageTopView initialization in onFinishInflate() 2015-02-17 18:04:52 +01:00
cketti 160b9eb354 Do MessageContainerView initialization in onFinishInflate() 2015-02-17 17:42:28 +01:00
cketti f887348953 Set background color of message view in one place 2015-02-17 04:15:10 +01:00
cketti e15cda8504 Fix "Show pictures" button functionality 2015-02-17 03:54:13 +01:00
cketti 68147880ce Move "Show pictures" button into MessageTopView
We can also get rid of the "Show message" and "Show attachments" buttons.
2015-02-17 02:32:20 +01:00
cketti c64ae008c1 Code cleanup 2015-02-17 01:54:40 +01:00
cketti 6b52f41e2c Better name for method argument 2015-02-17 01:43:53 +01:00
cketti e16f8af667 Don't pass Fragment reference to MessageContainerView 2015-02-17 01:30:41 +01:00
cketti 15cb58fe42 Add placeholder for application ID to K9FileProvider 2015-02-17 00:58:03 +01:00
cketti 9659bee8c5 Merge branch 'master' into pgp_mime_preparations 2015-02-17 00:56:42 +01:00
cketti ebef8eccb9 Add missing placeholder for application ID 2015-02-17 00:45:36 +01:00
Art O Cathain ffb4507776 Enums instead of int/String constants (#547)
simplify and add logging

simplify

use == for enum comparison to avoid type mistakes

enum name needs to match previous constant

simplify

Address review comments - formatting, and remove superfluous comment

Shorten DeletePolicy values since not used in settings strings; import enums to reduce clutter

fix whitespace

remove comment per review

address review comment

review comments

remove another superfluous qualification

Last changes
2015-02-16 22:22:05 +00:00
cketti 24e6b39dc0 Stop using Intent.setClassName()
Use the type-safe Intent constructor to reference internal classes.
2015-02-16 22:44:55 +01:00
cketti 461778ed11 Merge branch 'configurable_application_id' 2015-02-16 21:53:44 +01:00
cketti 1301645387 Use different application ID for debug builds
This way debug builds can be installed next to the release version.
2015-02-16 21:45:04 +01:00
cketti ebeed31705 Fix search for non-default application ID 2015-02-16 21:38:16 +01:00
cketti d703ac9148 Revert substitution of "com.fsck.k9" in implementation details
We don't need to change strings that are not exposed to the system/users.
2015-02-16 21:02:52 +01:00
cketti 9f1ecf7220 Merge pull request #555 from BombTeam/UnusedImportsRemoval
Remove unused import
2015-02-16 19:47:38 +01:00
cketti b4900cc6af Merge pull request #551 from BombTeam/remove-redundant-cast
Remove redundant casts
2015-02-16 18:46:15 +01:00
cketti 62c2894fce Merge pull request #544 from BombTeam/StringGlitchCorrection
Fix height of buttons in wizard screens
2015-02-16 18:30:51 +01:00
Valentin CAULIER 80f8e4a81d Removing java.util.Arrays import 2015-02-16 17:37:15 +01:00
Levrifon 0d0b80f142 Added placeholder for application ID 2015-02-16 16:53:38 +01:00
Marine c4e202ecd9 remove redundant assignment to FolderSettings
removed null affectation because it’s done automatically
2015-02-16 15:31:03 +01:00
Marine 9bf546b33b removed redundant assignment to AccountSetupBasics
removed null affectation because it’s done automatically
2015-02-16 15:25:36 +01:00
Marine f5cac2c71f remove redundant cast Button to WelcomeMessage
findViewById(R.id.next) and
findViewById(R.id.import_settings)
are Button so it’s redundant to cast it to Button
2015-02-16 15:10:34 +01:00
Marine 63047e0ac6 remove redundant cast (Button)
findViewById(R.id.pop) ,
findViewById(R.id.imap) and
findViewById(R.id.webdav)
are Button so it’s redundant to cast it to Button
2015-02-16 15:03:11 +01:00
Marine 980d799087 Rename from Searchfield to SearchField 2015-02-16 14:07:41 +01:00
qvandekadsye 9b1a0b3614 Applying "match-parent" value to "next" buttons. 2015-02-16 13:48:31 +01:00
cketti aae71125c6 Merge pull request #523
Make sure to keep font meta info on view recycling
2015-02-15 17:43:11 +01:00
cketti 9df1a3ee80 Potentially avoid creating new Typeface instances 2015-02-15 17:38:10 +01:00
cketti ba1fc1305f Merge branch 'master' into pgp_mime_preparations 2015-02-14 05:10:15 +01:00
cketti 9c3cab2354 Don't show changelog dialog during UI tests
This will hopefully make the UI tests more stable.
2015-02-14 04:23:54 +01:00
cketti 8d510e96a9 Merge pull request #543 from gburca/master
Fixes issue 6703
2015-02-13 18:48:59 +01:00
qvandekadsye 5013f36ba4 re-correcting String glitch by changing layout-height attribute 2015-02-13 15:21:09 +01:00
Gabriel Burca 9ba2725ab1 Fixes issue 6703
- onPrepareActionMode must be called before computeBatchDirection
  because computeBatchDirection ends up referencing mMarkAsRead /
  mMarkAsUnread and mFlag / mUnflag which could be null otherwise.
2015-02-12 21:27:44 -06:00
cketti e0abcc3f67 Decouple MessageCryptoHelper from MessageList 2015-02-09 20:02:16 +01:00
cketti 5d3cdc2724 Remove unused variable 2015-02-09 19:44:33 +01:00
cketti bb3f84fda6 Move MessageCryptoAnnotations to upper level 2015-02-09 19:43:23 +01:00
cketti d301efea58 Move MessageCryptoHelper 2015-02-09 19:37:46 +01:00
cketti 3a527cbcf6 Decouple MessageCryptoHelper from MessageViewFragment 2015-02-09 19:35:53 +01:00
cketti 948cb971ad Rename method
Since the crypto code no longer lives in MessageViewFragment it shouldn't
make references to what happens after its work is done.
2015-02-09 19:05:56 +01:00
cketti 5c036e2991 Simplify control flow 2015-02-09 19:05:45 +01:00
cketti 26eb1f52e5 Don't attempt to verify/decrypt if no crypto provider is configured 2015-02-06 23:26:46 +01:00
cketti b515e947cf Fix typo 2015-02-06 23:12:26 +01:00
cketti ee1180e34c Use Part from MessageViewContainer for K9WebViewClient
Now K9WebViewClient can find encrypted attachments referenced by Content-ID.
2015-02-06 20:11:58 +01:00
cketti cadac6dd89 Add reference to root part to MessageViewContainer 2015-02-06 20:04:03 +01:00
cketti 49c4115e46 Refactor code; no functional changes 2015-02-06 20:01:14 +01:00
cketti 3377e50352 Fix code style 2015-02-06 19:57:30 +01:00
cketti fce12b2450 Remove unused constructor 2015-02-06 19:53:39 +01:00
cketti 1a20ca06f1 Handle "cid:" URIs in HTML message body 2015-02-06 18:31:26 +01:00
cketti 80221dace8 Mark DownloadImageTask as deprecated 2015-02-04 21:43:45 +01:00
cketti 1f27897679 Extract methods to make code more readable 2015-02-04 21:42:09 +01:00
cketti 5175ff9df4 Extract method 2015-02-04 21:17:54 +01:00
cketti 9814442de4 Extract constant for default file name 2015-02-04 21:16:08 +01:00
cketti 36abde2c0b Extract method 2015-02-04 21:14:04 +01:00
cketti 937ca7e17a Move inner class DownloadImageTask to upper level 2015-02-04 21:07:54 +01:00
cketti d7da286098 Remove unused imports 2015-02-04 21:03:25 +01:00
cketti 0241001c63 Display attachment size for decrypted parts 2015-02-01 05:41:40 +01:00
cketti 19db6c703b Don't display -1 as attachment size 2015-02-01 04:24:02 +01:00
cketti 474efa1831 Fix NullPointerExceptions introduced by conditionally inflating the crypto layout 2015-02-01 00:21:52 +01:00
Vincent Breitmoser dc8fd39c7e move crypto data into an annotation structure, and fix pgp/inline
note that we currently lack proper confirmation about whether data was
actually decrypted or not, so for now we always assume it wasn't
2015-01-30 16:16:11 +01:00
Vincent Breitmoser 4bec165fdc preliminary support for pgp/inline 2015-01-30 16:11:57 +01:00
cketti de8da4dab4 Write decrypted bodies to temporary files
Use FileProvider to be able to open decrypted attachments
2015-01-30 14:27:33 +01:00
Vincent Breitmoser e8c591e6be ignore application/pgp-signature parts for display 2015-01-30 13:19:53 +01:00
Vincent Breitmoser 0374dc9cb1 display pendingIntent button when there is a pendingIntent only 2015-01-30 11:30:36 +01:00
Vincent Breitmoser a0b4faf688 pass OpenPgpError to display, delete old layout 2015-01-30 10:55:06 +01:00
Vincent Breitmoser d57f6c0ed5 buffer data after decryption before mime parsing 2015-01-30 10:34:46 +01:00
cketti 7158abe7ff Use same version of build tools we use in main project 2015-01-30 10:28:14 +01:00
Dominik Schürmann a00a119e18 Update OpenPGP API lib to newest version 2015-01-29 20:53:25 +01:00
Vincent Breitmoser 3077e6a2d7 close piped streams after use 2015-01-29 20:24:59 +01:00
cketti 0a07250417 Fix class name 2015-01-29 20:07:30 +01:00
Vincent Breitmoser d678ccc160 extract crypto methods from MessageViewFragment into MessageCryptoHelper 2015-01-29 20:00:28 +01:00
Vincent Breitmoser 00b7b74878 pass pendingIntent, and some refactoring 2015-01-29 19:27:48 +01:00
Vincent Breitmoser 712acf4481 early support for detached signatures 2015-01-29 19:01:44 +01:00
cketti 38d3564c57 Merge pull request #537 2015-01-29 17:48:28 +01:00
cketti 41ac5a9fed Fix indentation 2015-01-29 17:47:37 +01:00
cketti edf75a32d8 Fix LocalMessageExtractorTest 2015-01-29 17:41:46 +01:00
cketti 8627e65cab Merge pull request #536 from ligi/ligi/refactor/pgp_utils
Test & refactor OpenPgpUtils
2015-01-29 17:32:59 +01:00
cketti fed15a01e5 Merge branch 'store_decryption_result' into pgp_mime_preparations
Conflicts:
	k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java
2015-01-29 16:43:50 +01:00
ligi 033c1502db Reduce LOC even more 2015-01-29 16:43:45 +01:00
ligi a51b608e5e Reduce MessageCompos LOC 2015-01-29 16:36:15 +01:00
ligi 828a580eb8 Move this View out ( the hosting class is already too long with ~4000 Lines ..) 2015-01-29 16:30:01 +01:00
ligi 4075b72b72 Cleanup and Optimize ( no need to prepare some layout when it is never used ) 2015-01-29 16:24:44 +01:00
cketti fbfa6d146f Pass OpenPgpSignatureResult to LocalMessageExtractor 2015-01-29 16:16:29 +01:00
Vincent Breitmoser ba79779758 break message into multiple MessageViewContainers 2015-01-29 16:10:03 +01:00
cketti 9e47686277 Code style fixes 2015-01-29 15:24:06 +01:00
cketti 7b67d054a4 Don't save reference to multipart/encrypted "root" in decrypted parts 2015-01-29 15:16:59 +01:00
Vincent Breitmoser 1046308a38 converge threads after decryption, and handle PendingIntents 2015-01-29 12:57:04 +01:00
ligi 4bc9d94831 Test & Refactor OpenPgpUtils 2015-01-29 12:30:19 +01:00
Vincent Breitmoser bcd570f884 always show OpenPgpHeader if crypto-provider is set 2015-01-28 18:11:51 +01:00
cketti 4827b4c437 Merge pull request #535 2015-01-28 17:42:34 +01:00
cketti 7f811fce2c First attempt at decrypting PGP/MIME messages 2015-01-28 17:24:05 +01:00
Dominik Schürmann 6f156498ed Callback to execute signature button click 2015-01-28 15:40:19 +01:00
cketti bb83fdc0e8 Add support for loading parts with DataLocation.ON_DISK 2015-01-28 15:29:49 +01:00
Dominik Schürmann fdc597aadf Display signer name and email 2015-01-28 15:22:38 +01:00
Dominik Schürmann 12cf19b63e SplitUserID into lib 2015-01-28 15:15:08 +01:00
Dominik Schürmann bc2fe2dbfe Update openpgp-api-library 2015-01-28 15:11:08 +01:00
Dominik Schürmann d112344780 Smaller sidebar 2015-01-28 15:09:19 +01:00
Dominik Schürmann 28e9c2a8ec Merge remote-tracking branch 'vincent/pgp_mime_preparations' into pgp_mime_preparations_view 2015-01-28 14:55:51 +01:00
Dominik Schürmann d46d355f69 OpenPGP status texts and sidebar improvements 2015-01-28 14:54:43 +01:00
Vincent Breitmoser cced35b3b8 parse MessageViewContainers from Parts (from dummy mime structure) 2015-01-28 14:35:18 +01:00
Dominik Schürmann e513af9529 Sidebar 2015-01-28 12:26:34 +01:00
Dominik Schürmann b781ace4fa OpenPGP header and sidebar tests 2015-01-28 11:30:29 +01:00
Dominik Schürmann 4c78d12fc6 Merge remote-tracking branch 'vincent/pgp_mime_preparations' into pgp_mime_preparations_view
Conflicts:
	k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java
2015-01-28 09:31:11 +01:00
Dominik Schürmann e3fef1af98 Work on new pgp header 2015-01-27 17:50:40 +01:00
Vincent Breitmoser 581d32acd6 show attachments inline 2015-01-27 17:33:50 +01:00
Vincent Breitmoser 445c978f31 extract header view for multiple MessageContainerViews (intermediate state) 2015-01-27 16:48:33 +01:00
Vincent Breitmoser 29ad0f0f99 rename SingleMessageView to MessageContainerView 2015-01-27 16:42:31 +01:00
Vincent Breitmoser 40b6228756 new MessageViewInfo structure (with transitional methods) 2015-01-27 12:55:47 +01:00
cketti d92be22ce3 Merge branch 'master' into pgp_mime_decrypt 2015-01-27 12:44:08 +01:00
cketti 74820a40db Merge pull request #534 from k9mail/openpgp_api_library_update
Update openpgp-api-library to latest version
2015-01-27 12:42:47 +01:00
cketti 74fdbb7859 Update openpgp-api-library to latest version 2015-01-27 12:15:47 +01:00
cketti 8f7f656355 Add method to find multipart/encrypted parts 2015-01-26 20:37:27 +01:00
cketti 0e03f262b3 Make sure to close underlying InputStream after decoding attachments 2015-01-25 20:06:29 +01:00
cketti 378acbd313 Write large message parts to file system
Actually, we just move the temporary file to avoid having to copy the
data to a new file.
2015-01-25 19:25:00 +01:00
cketti 977d15c190 Refactor to improve readability 2015-01-23 15:08:56 +01:00
cketti 6825eafb87 Make column 'message_parts.data' a BLOB 2015-01-23 14:41:29 +01:00
cketti 564e2432e1 Get size of decoded body content when saving
Before downloading we show the encoded size of attachments. After download we
strip the transport encoding to find out the size of the decoded content.
2015-01-23 03:58:06 +01:00
cketti 98bdf54672 Don't save empty multipart body
This will correctly mark the body as missing when the message is written to
the database.
2015-01-23 03:57:13 +01:00
cketti 74d09943c0 Use MimeMessageHelper.setBody() when parsing BODYSTRUCTURE
This will correctly set the MIME type of the part containing the body.
Otherwise multiparts end up having a content type of text/plain (default)
in the database... oops.
2015-01-23 03:55:54 +01:00
cketti d2d85393d3 Save attachment count 2015-01-22 06:12:26 +01:00
cketti 5e4743bf66 Extract preview of message text 2015-01-22 05:18:50 +01:00
cketti fe7b88f7c2 Work around the BinaryTempFileBodyInputStream mess 2015-01-22 04:56:08 +01:00
cketti 64e92ab1c1 Remove unused LocalAttachment* classes 2015-01-22 04:18:20 +01:00
cketti c9b2ec533c Add MessagePreviewExtractor 2015-01-21 01:21:02 +01:00
cketti 23c9398c03 Merge branch 'master' into pgp_mime_preparations
Conflicts:
	k9mail/src/androidTest/java/com/fsck/k9/mailstore/LocalMessageTest.java
2015-01-20 18:35:07 +01:00
cketti 395b70fa22 Remove unused code 2015-01-20 16:37:30 +01:00
cketti da51bdf1b3 Save attachments in background thread 2015-01-19 23:35:14 +01:00
cketti a7b16c1210 Refresh thumbnail after downloading attachment 2015-01-19 23:08:41 +01:00
cketti 9363c5b276 Download missing parts before viewing or saving 2015-01-19 22:37:15 +01:00
cketti 585d9cbe7f Fix "Download complete message" 2015-01-18 01:33:50 +01:00
cketti 1e628e7177 Reduce exposure of SingleMessageView internals 2015-01-16 23:37:37 +01:00
cketti 658657447e Fix viewing attachment with alternative MIME type 2015-01-16 23:37:37 +01:00
cketti de2eb25446 Use Glide for thumbnail generation + image loading 2015-01-16 23:37:37 +01:00
cketti cb94b5b192 Store attachment information in LocalBodyPart 2015-01-16 05:05:11 +01:00
cketti ac365567ee Replace dummy URI in AttachmentViewInfo instances
This is a first step towards fixing viewing of attachments.
2015-01-16 00:12:47 +01:00
cketti 41bd420213 Include database ID in message parts reconstructed from the database 2015-01-15 23:48:11 +01:00
cketti 8fce9e3654 Move functionality from AttachmentView to AttachmentController 2015-01-15 11:37:23 +01:00
cketti 087238f507 Move AttachmentView to 'messageview' package 2015-01-15 08:25:43 +01:00
cketti 2e05127c97 Use a Loader to extract text of a message in a background thread 2015-01-14 09:58:31 +01:00
Jan Berkel ecd316d0af Simplify ImapConnection#connect 2015-01-14 04:41:21 +01:00
Jan Berkel 9523a589fc use `@Test(expected =` 2015-01-14 04:31:36 +01:00
Jan Berkel c79256684d simplify gradle config 2015-01-14 04:31:32 +01:00
Jan Berkel dfb025033d Convert remaining tests to JUnit4 2015-01-14 04:05:42 +01:00
Jan Berkel 4808406739 Make deleteCertificate null-safe 2015-01-14 03:45:09 +01:00
Jan Berkel 833d9c5de8 Base class is already annotated 2015-01-14 03:34:57 +01:00
Jan Berkel 79b03b5e4f Remove account after creation
* Avoid side-effects in static preferences which might interfere with other tests
2015-01-14 03:30:38 +01:00
Jan Berkel 4e964e271c Convert to JUnit4 test 2015-01-14 03:09:48 +01:00
Jan Berkel 0153766dd5 Convert to JUnit4 2015-01-14 03:09:00 +01:00
Jan Berkel 0f312f012e Create test mailboxes 2015-01-14 03:08:44 +01:00
Jan Berkel 055d4104b7 log ChangeLog click failure 2015-01-13 16:12:39 +01:00
Jan Berkel a15583a080 Start server early 2015-01-13 15:52:31 +01:00
Jan Berkel de4b6d1076 Merge pull request #531 from jberkel/imap-tests
Update greenmail + add more tests
2015-01-13 11:43:07 +01:00
Jan Berkel bdbe976396 Test connection w/o server 2015-01-13 10:09:42 +01:00
Jan Berkel e98f323222 Initialize settings in setUp() 2015-01-13 10:05:15 +01:00
Jan Berkel f7da704007 WS 2015-01-13 09:34:13 +01:00
cketti 78ed2a23b1 Use a Loader to load the message to view from the database 2015-01-13 04:17:25 +01:00
Jan Berkel 60070b7883 Add more tests + descriptive names 2015-01-13 01:59:51 +01:00
Jan Berkel 111212b391 Setup and tear down server between tests 2015-01-13 01:11:09 +01:00
Jan Berkel 7958467503 Convert from thread to AsyncTask for espresso tests 2015-01-13 01:11:09 +01:00
Jan Berkel b481d3f978 Adding tests for IMAP connection, use greenmail snapshot 2015-01-13 01:10:56 +01:00
cketti 787c014265 Create new package for UI code related to message viewing 2015-01-12 22:46:56 +01:00
cketti 1bf159a300 Get rid of unused LocalTextBody 2015-01-12 22:09:55 +01:00
cketti bcd64017e3 Extract text to display before viewing the message 2015-01-12 21:52:44 +01:00
cketti 4db57dfc85 Merge pull request #529 from k9mail/ignore_meta_refresh
Sanitize HTML to remove meta refresh
2015-01-11 11:35:06 +01:00
cketti 63abf05776 Sanitize HTML to remove meta refresh
Using
  <meta http-equiv="Refresh" content="1; URL=http://example.com/">
in a HTML message causes WebView to load the URL in the default browser.
Overriding WebViewClient.shouldOverrideUrlLoading() allows us to cancel
loading this URL. Sadly, I found no way to find out whether the method was
called because of a meta refresh or because the user clicked on a link.

So now we're using HtmlCleaner to parse the HTML and remove all "meta" elements
containing an "http-equiv" attribute with a value of "refresh".
2015-01-11 11:29:53 +01:00
cketti 2532362ed5 Add test for updating a message with a missing part 2015-01-10 04:39:02 +01:00
cketti bd97004ebd Fix downloading/saving single message parts 2015-01-10 04:38:57 +01:00
cketti 743e640d8c Remove references to 'text_content' and 'html_content' 2015-01-10 01:22:39 +01:00
cketti ce862c88f8 Change AttachmentProvider to use the new database structure 2015-01-07 02:34:44 +01:00
cketti e5f0bec6bc Get rid of "backward compatibility" in AttachmentProvider 2015-01-07 00:16:37 +01:00
cketti c5ba202a56 Code style fixes 2015-01-07 00:13:28 +01:00
cketti 34b5d56ab1 Get rid of 'attachments' table 2015-01-06 23:59:58 +01:00
cketti 1a5ecfea1d Also delete local messages when using "clear messages" on an account
We have been throwing away all attachments already, so it doesn't make
too much sense to keep local messages. And when we're not keeping local
messages we can remove all entries from the 'threads' table.
2015-01-06 23:48:22 +01:00
cketti 30e37000f9 Remove remnants of the "headers" table 2015-01-06 21:36:31 +01:00
cketti d7edb0ed4f Minimal version that reconstructs original message from the database
This change breaks all kinds of things, e.g.
- deleting messages
- updating messages
- downloading attachments
- deleting attachments
- searching in message bodies
2015-01-06 03:20:38 +01:00
Jan Berkel c7229e4724 Enable lint checks for k9mail-library 2015-01-05 23:26:36 +01:00
cketti 523ebd0f2a Remove 'dirty' check for LocalMessage 2015-01-05 02:25:17 +01:00
cketti 3eb25a011f Don't automatically create Message-ID when none is found 2015-01-05 02:25:17 +01:00
cketti d7085a2f07 Properly decode the body in MessageExtractor.getTextFromPart() 2015-01-05 00:57:25 +01:00
cketti abbad18283 Code style fixes 2015-01-05 00:45:05 +01:00
cketti 04b5b4a230 Merge pull request #527 from notfoss/patch-1
Add Zoho Mail (personal) to providers.xml
2015-01-04 01:05:17 +01:00
notfoss 4bc003e173 Add Zoho Mail (personal) to providers.xml
Added settings for Zoho Mail personal account (@zoho.com).
2015-01-03 16:43:01 +05:30
cketti 7b5c73b43c Add (failing) test for reconstructing a message from the database 2015-01-03 09:38:49 +01:00
cketti ddd78bd3e3 Merge branch 'findbugs_fixes' 2015-01-03 06:33:43 +01:00
cketti 72f022d7ed Ignore newline in format string Findbugs warnings 2015-01-03 05:41:29 +01:00
cketti 57ad0fd6b3 Remove unused implementation from abstract class 2015-01-03 04:39:25 +01:00
cketti ad8da49991 Remove unused code 2015-01-03 04:27:27 +01:00
cketti b6315b15b0 Fix equals() method to work with all objects 2015-01-03 04:10:15 +01:00
cketti 6804ee04e3 Fix some MS_SHOULD_BE_FINAL Findbugs warnings 2015-01-03 03:51:47 +01:00
cketti 65d2de0fcc Fix bug caused by edge case of Math.abs() 2015-01-03 03:51:47 +01:00
cketti 928c7f33a3 Add missing hashCode() method to SearchCondition 2015-01-03 03:51:47 +01:00
cketti e45d780c6f Fix WebDavFolder.equals() 2015-01-03 02:12:37 +01:00
cketti b69bba01da Fix DM_BOXED_PRIMITIVE_FOR_PARSING findbugs warning 2015-01-03 02:02:54 +01:00
cketti e2a9dd3042 Upgrade to Espresso 2.0 2015-01-02 23:43:46 +01:00
cketti 82736f3a8b Merge pull request #524 from k9mail/merge_pgp_mime_branch
Merge changes from PGP/MIME repository
2015-01-02 22:04:28 +01:00
Jan Berkel 78758714c2 Add testcase for #525 2014-12-31 00:43:28 +01:00
m0viefreak e8c6a56fd2 Use correct sub-part of a multipart message when getting text.
946565347a passed 'this' to
getTextFromPart() which could be a multipart. This caused
all multipart messages to show 'No text' as the body.

Fix it by passing it the correct 'part' that was found.
2014-12-25 14:48:05 +01:00
Jan Berkel fe8e779b32 Reformat 2014-12-23 10:15:24 +01:00
cketti 704cb35d7e Fix Espresso tests 2014-12-22 23:52:34 +01:00
cketti c96a11212e Update dependencies 2014-12-22 23:09:05 +01:00
Jan Berkel b0d401c3b7 Added note about expected method parameter format
8194c20ffe (commitcomment-9069167)
2014-12-22 22:37:17 +01:00
cketti 6c172f94a1 Add missing global settings to settings export 2014-12-22 19:47:15 +01:00
cketti 152e0a0530 Revert ImapStore URI change
Reverts changes introduced with commit 8194c20ffe
Adds test to make sure usernames/passwords with special characters encode/decode properly.
2014-12-22 18:24:22 +01:00
cketti 703c007fc8 Fix code style 2014-12-22 17:33:48 +01:00
Jan Berkel 8194c20ffe Fix IMAP uri decode when user/pw contains ':' 2014-12-21 11:52:05 +01:00
Frank Du c473ddc90a Make sure to keep font meta info on view recycling 2014-12-20 14:50:15 -08:00
cketti 4f8fc5bc5b Merge remote-tracking branch 'k9mail_pgp_mime/master'
Fixed lots of conflicts
2014-12-20 08:07:46 +01:00
cketti 7752f42db6 Merge branch 'gradle_only_and_k9mail_library'
Conflicts:
	k9mail-library/src/main/java/com/fsck/k9/mail/transport/imap/ImapSettings.java
	src/com/fsck/k9/mail/store/imap/ImapSettings.java
	src/com/fsck/k9/mail/transport/imap/ImapSettings.java
2014-12-20 04:46:14 +01:00
Jan Berkel ae6f1fa299 Cleanup 2014-12-20 03:10:31 +01:00
Jan Berkel 1bd74ad263 Move settings to the right place 2014-12-20 03:03:06 +01:00
cketti 52b3974c4f Run checkstyle and findbugs on k9mail-library 2014-12-20 01:49:09 +01:00
cketti 105bca735b Move 'mail' package to library project 2014-12-20 01:31:33 +01:00
Jan Berkel 6a24aca343 Move TracingPowerManager into mail package 2014-12-20 00:50:09 +01:00
cketti 9e7721ca62 Make ckChangeLog an external dependency 2014-12-20 00:33:00 +01:00
cketti 348051cb95 Remove outdated docs 2014-12-20 00:21:02 +01:00
cketti 4d61a6407d Move lint configuration file into 'config' folder 2014-12-20 00:10:38 +01:00
cketti 0a6046cae7 Update Transifex client configuration 2014-12-20 00:10:38 +01:00
cketti 7d3cea87f9 Use default directory structure for tests-on-jvm subproject 2014-12-20 00:10:38 +01:00
cketti 1212f9d0f2 Move main application from root project to subproject 'k9mail' 2014-12-20 00:10:38 +01:00
cketti cfbebdb4b6 Remove files not necessary for Gradle builds 2014-12-19 23:19:19 +01:00
Jan Berkel 2eecb2d2c5 Break ImapConnection#open into smaller methods 2014-12-19 11:28:19 +01:00
Jan Berkel 6ed52ac551 Fix some warnings in ImapStore / Pop3Store / WebDavStore 2014-12-19 10:13:32 +01:00
Jan Berkel e214dbbd99 Tighten types in Folder#fetch(…) 2014-12-19 09:54:17 +01:00
Jan Berkel 98c1935c85 Reformat according to style 2014-12-19 09:48:02 +01:00
cketti 4b0d016bb7 Add support for code coverage to tests-on-jvm project 2014-12-19 03:58:03 +01:00
cketti 2011655344 Update build tools version for subprojects 2014-12-19 02:57:39 +01:00
cketti df8a823e41 Add support for recording code coverage 2014-12-18 14:15:45 +01:00
cketti 3760ca95d5 Add checkstyle to tests-on-jvm project 2014-12-18 14:15:45 +01:00
cketti 3f7fc83d58 Add findbugs to Gradle build 2014-12-18 13:39:59 +01:00
cketti fd02085946 Remove JUnitReportTestRunner 2014-12-18 13:39:59 +01:00
Jan Berkel 043df7e7c5 Decouple K9mail logging 2014-12-18 12:48:10 +01:00
Jan Berkel 714acabf83 Inverse dependencies 2014-12-18 12:22:01 +01:00
Jan Berkel 66dd4990b1 Move tests to JVM + convert to modern syntax 2014-12-18 12:00:49 +01:00
Jan Berkel 0a6920c63e Refactor IMAP code + tests 2014-12-18 11:24:43 +01:00
Jan Berkel 6a1fee90ee Use 1.6 source compatibility for now 2014-12-18 10:07:18 +01:00
Jan Berkel 0f476978ce mail.store.* cleanups 2014-12-18 09:33:09 +01:00
Jan Berkel 3c38cb2d7f Not needed anymore 2014-12-17 20:38:41 +01:00
Jan Berkel e1fe6a97e1 Leftover 2014-12-17 20:34:17 +01:00
Jan Berkel 51bc464449 Decouple AuthType / ConnectionSecurity from main app 2014-12-17 20:32:41 +01:00
cketti 9c7776d289 Remove leading/trailing spaces from search string
Some keyboards insert a trailing space when doing word completion.
2014-12-17 19:17:58 +01:00
Jan Berkel 2f30b3956d Dependency Inversion, remove K9.app references 2014-12-17 17:42:22 +01:00
Jan Berkel b36c788ce0 Move ImapConnection out 2014-12-17 17:16:18 +01:00
Jan Berkel 16f8a3ef14 Remove trusted socket factory statics 2014-12-17 16:43:55 +01:00
Jan Berkel 27e0c75021 Move exception error strings out of mail package 2014-12-17 16:24:52 +01:00
Jan Berkel ac33de6310 Fix EOLConvertingOutputStream + add test
Add test for another EOLConvertingOutputStream edge case

Fix bug in EOLConvertingOutputStream

Swap order

Simplify
2014-12-17 14:27:37 +01:00
Jan Berkel 7c79e7c6b5 Forward test 2014-12-17 12:57:18 +01:00
Jan Berkel cc6c6bf096 Tests-on-jvm should be a subproject 2014-12-17 12:47:39 +01:00
Jan Berkel 5248350953 Clean up tests 2014-12-17 12:46:53 +01:00
cketti b091ae4fa0 Merge pull request #515 from k9mail/avoid_using_application
Favor Context over Application
2014-12-17 05:54:00 +01:00
cketti e447257414 Stop using K9.app where easily possible 2014-12-17 05:25:46 +01:00
cketti 328405419a Don't require Application when a Context instance will do 2014-12-17 05:22:24 +01:00
cketti 86487a738d Make Gradle task testOnJVM only depend on assembleDebug 2014-12-17 03:17:08 +01:00
cketti deb11b2226 Merge pull request #516 from k9mail/untangle-network-code
Untangle network code
2014-12-17 01:45:16 +01:00
Jan Berkel 231684936b break/centralize dependencies to K9 2014-12-16 12:54:12 +01:00
Jan Berkel 245a6330ed Move logic into MessageHelper and add tests 2014-12-16 12:17:25 +01:00
Jan Berkel 44f6a2479b Remove reference to K9#hideTimeZone() + test 2014-12-16 12:07:27 +01:00
cketti 946565347a Revert adding methods to Message and Part 2014-12-16 06:04:39 +01:00
cketti d24998d584 Fix tests-on-jvm 2014-12-16 05:16:09 +01:00
cketti 62c5ac8e5f Rename 'accountId' to 'accountUuid' 2014-12-16 04:02:54 +01:00
cketti 23d9310c61 Remove getUuid() from StoreConfig 2014-12-16 03:32:57 +01:00
Jan Berkel c608258494 Unused imports 2014-12-15 13:46:23 +01:00
Jan Berkel 15a4c90f27 Update javadoc 2014-12-15 13:20:42 +01:00
Jan Berkel b443af43ae Cleanup 2014-12-15 13:01:13 +01:00
Jan Berkel 2a2e18e8b6 WS / visibility 2014-12-15 12:45:04 +01:00
Jan Berkel 36ef6df018 Remove unused method 2014-12-15 12:42:05 +01:00
Jan Berkel 7d6e6b8abe MimeUtility / Message refactor
* break MimeUtility class into manageable pieces (MessageExtractor/CharsetSupport)
 * move HTML related code out of the mail package
2014-12-15 12:26:06 +01:00
Jan Berkel 476cb1d4ce Tidy responsibilities 2014-12-14 15:58:08 +00:00
Jan Berkel 238c1650c5 Remove URLEncodingHelper dependency 2014-12-14 15:54:27 +00:00
Jan Berkel 8ef9eae0d6 local -> mailstore 2014-12-14 15:28:42 +00:00
Jan Berkel bd697bb56d getUuid() -> getAccountUuid() 2014-12-14 15:26:38 +00:00
cketti 96413a50a3 Merge pull request #517 from pylerSM/patch-1
Removed useless line
2014-12-14 04:20:46 +01:00
cketti 209072398e Merge pull request #518 from frankdu/clean-up
Remove K9FragmentActivity
2014-12-14 04:13:44 +01:00
Frank Du ff70bf4e22 Remove K9FragmentActivity
Because it's same with K9Activity now, no need to keep it.

Test Plan:
./gradlew installDebug, and test the MessageList activity
2014-12-13 16:05:41 -08:00
pyler 39fc962da0 Removed useless line 2014-12-13 13:56:22 +01:00
Jan Berkel 12248bca92 Fix test location 2014-12-12 15:32:36 +00:00
Jan Berkel 54d62eb7b9 Naming 2014-12-12 15:19:13 +00:00
Jan Berkel b644194a3d Fix LocalMessage equality/hash 2014-12-12 15:16:38 +00:00
Jan Berkel 40041ac0e0 Move local message code to local package
+ cut some helper dependencies
2014-12-12 15:02:59 +00:00
Jan Berkel 2e98ff56e5 Break dependencies 2014-12-12 13:35:36 +00:00
Jan Berkel 0024f39bc6 Local messages 2014-12-12 13:04:59 +00:00
Jan Berkel 708fb57c04 Move things into local 2014-12-12 12:49:26 +00:00
Jan Berkel 6264527abc Remove Preferences dependency 2014-12-12 12:42:48 +00:00
Jan Berkel 9f16b9f465 Move SSL code into package 2014-12-12 12:35:17 +00:00
Jan Berkel 2536719749 Naming 2014-12-12 07:03:14 +00:00
Jan Berkel 5af649c271 Avoid cast 2014-12-12 06:54:34 +00:00
Jan Berkel 9fd722d7cd Split message code into Local/Remote
The remote network code does not need to be aware of concepts like
Accounts etc.
2014-12-12 06:47:26 +00:00
Jan Berkel 6f4610dd5b Fix visibility 2014-12-12 00:30:41 +00:00
Jan Berkel edfd20bf85 Fix visibility 2014-12-12 00:18:16 +00:00
Jan Berkel e59adf46c0 Package visibility 2014-12-12 00:08:25 +00:00
Jan Berkel 12291bceb5 Move database code into correct package 2014-12-11 23:56:02 +00:00
Jan Berkel ba4dd24bd5 Package visibility 2014-12-11 23:32:38 +00:00
cketti f89b0548a6 Add test to verify a signed PGP/MIME message 2014-12-12 00:05:02 +01:00
Jan Berkel ca10e4d94a Use TextUtils.isEmpty 2014-12-11 21:56:26 +00:00
cketti 987b8b17b1 Merge pull request #21 from k9mail/reconstruct_original_message_in_memory
Reconstruct original message in memory
2014-12-11 22:00:57 +01:00
cketti 37b0666f4a Remove Eclipse settings 2014-12-10 18:28:54 +01:00
cketti f94491a359 Add code style settings for Android Studio 2014-12-10 18:24:27 +01:00
cketti d1d9f1ea19 Merge remote-tracking branch 'upstream/master'
Conflicts:
	build.gradle
	gradle/wrapper/gradle-wrapper.properties
	plugins/openpgp-api-library/build.gradle
2014-12-09 00:57:27 +01:00
cketti 0ce7c911e5 Update compileSdkVersion to 21 and Gradle Android Plugin to 1.0.0 2014-12-09 00:41:10 +01:00
cketti e374538110 Store multi part preamble as byte array 2014-12-08 17:38:30 +01:00
cketti 3919c9d2d6 Save multi part epilogue in MimeMultipart 2014-12-08 16:48:18 +01:00
cketti d1d7b60a09 Add helper method to decode message bodies
Depending on whether a Body implements RawDataBody (which indicates the class
retains the original encoding) the helper method either strips the transfer
encoding or simply returns the result of Body.getInputStream().

This should restore the original functionality. So saving messages in the
database should work fine again.
2014-12-08 16:32:23 +01:00
cketti f7d3eaa006 Fix setUsing7bitTransport() functionality for BinaryTempFileBody 2014-12-08 16:32:23 +01:00
cketti 9f4f0cf6a8 Modify BinaryTempFileBody to retain the encoded body
For now this breaks a lot of things, e.g. saving messages to the database
and making messages 7-bit safe.
2014-12-08 16:32:23 +01:00
cketti d32d6eed0e Move "magic" from Part.setBody() implementations to MimeMessageHelper.setBody()
Now adding message bodies during parsing won't set/modify headers.
2014-12-08 16:32:23 +01:00
cketti 51a60b5ad3 Modify ReconstructMessageTest to highlight more problems 2014-12-08 16:32:23 +01:00
cketti 2404b80b04 Fix MessageTest now that we preserve line breaks in headers 2014-12-08 16:32:23 +01:00
cketti bcb6c75c2e Add support for storing raw header fields 2014-12-08 16:32:23 +01:00
cketti 8630bb0ad4 Add simple test to check if writing a parsed message leads to input data 2014-12-08 16:32:23 +01:00
cketti 825c508ff6 Merge pull request #19 from k9mail/art/update-gradle
Update to be compatible with latest Android Studio
2014-12-07 20:52:26 +01:00
Art O Cathain 68c95d0283 Update to be compatible with latest Android Studio 2014-12-07 14:22:03 +00:00
cketti 878189baec Merge pull request #17 from k9mail/art/espresso-e2e-tests-2
Espresso tests for account setup
2014-12-07 04:40:47 +01:00
cketti 272a4bc1cf Bump version to 5.103 2014-12-06 01:37:08 +01:00
cketti 7cbea6e4b2 Prepare changelog for 5.103 2014-12-06 01:36:10 +01:00
cketti 3b2d625a09 Pull updated translations from Transifex 2014-12-06 01:21:57 +01:00
cketti 410edd7107 Change wording of setting option 2014-12-06 00:42:46 +01:00
cketti 7177afa4d2 Update Gradle Android Plugin to 1.0.0-rc4 2014-12-05 23:04:51 +01:00
Andrew Chen 105948d78c Oops, checking wrong platform capability. 2014-12-04 19:14:22 -08:00
Andrew Chen 14edb093f2 Merge pull request #512 from k9mail/lollipop_lock_screen_notifications
Lollipop lock screen notifications
2014-12-04 19:01:04 -08:00
Andrew Chen 1fa6e117e1 Add Lollipop lock screen notifications.
Add vector versions for some notification icons (yay Illustrator)
Add comments reminding people to add their settings to GlobalSettings.
<plurals> support for notification_new_messages_title
Not sure why #ffffffff is resulting in black with targetSdk 17.
2014-12-04 18:54:22 -08:00
cketti b51ad495ed Merge pull request #513 from maniac103/fix-cert-exception-handling
Don't throw CertificateValidationException for all SSLExceptions.
2014-12-04 16:17:44 +01:00
Danny Baumann aaf3963567 Don't throw CertificateValidationException for all SSLExceptions.
An interrupted connection attempt to the server yields an SSLException
as well, like this:

E/k9      ( 6937): Caused by: javax.net.ssl.SSLHandshakeException: Connection closed by peer
E/k9      ( 6937):      at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
E/k9      ( 6937):      at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:302)
E/k9      ( 6937):      at com.android.org.conscrypt.OpenSSLSocketImpl.waitForHandshake(OpenSSLSocketImpl.java:598)
E/k9      ( 6937):      at com.android.org.conscrypt.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:560)
E/k9      ( 6937):      at com.fsck.k9.mail.store.ImapStore$ImapConnection.open(ImapStore.java:2459)

We don't want the user to notify of 'certificate problems' in that case.
Fix it by checking whether the SSLException was actually triggered by a
CertificateException.
2014-12-04 13:16:33 +01:00
Andrew Chen 54d6566fb5 Force gradlew (and other shell scripts) to use lf line endings. 2014-12-03 18:50:16 -08:00
cketti 2c81495aea Bump version to 5.102 2014-11-27 21:47:51 +01:00
cketti 68cee3e9a3 Update changelog for 5.102 2014-11-27 21:34:29 +01:00
cketti 98b5d63909 Merge branch 'open_attachment_improvements'
Conflicts:
	src/com/fsck/k9/helper/Utility.java
2014-11-27 21:03:15 +01:00
Art O Cathain 854b1b3ffc Add end-to-end tests using Espresso 2014-11-23 15:26:05 +00:00
Art O Cathain 552e552e88 Add unit tests for FileHelper.sanitizeFilename() 2014-11-18 23:50:14 +01:00
cketti 56c30095e2 Don't use dummy file path when resolving intents 2014-11-18 23:02:20 +01:00
cketti d9b6e10cbe Change the way the best view intent is determined
First we try the original MIME type unless it's application/octet-stream.
Then we try the MIME type inferred from the attachment's file extension.
Then we fall back to application/octet-stream.

In all cases we first try the content:// URI, then a file:// URI.
2014-11-18 22:55:51 +01:00
cketti a725099693 Merge pull request #499 from k9mail/remove_apg_interface
Remove APG legacy interface
2014-11-12 21:08:46 +01:00
cketti 526fa443a8 Merge branch 'pr/505'
Get more dependencies from jCenter
2014-11-12 20:55:14 +01:00
cketti 6542ba3a72 Reorder dependencies 2014-11-12 19:53:05 +01:00
cketti c252335d2e Use jutf7 1.0.0 since 1.0.1-SNAPSHOT doesn't include any relevant improvements 2014-11-12 19:49:28 +01:00
cketti 9b61fe0f0e Merge remote-tracking branch 'k9mail_pgp_mime/master'
Conflicts:
	src/com/fsck/k9/activity/AccessibleEmailContentActivity.java
2014-11-12 19:22:53 +01:00
cketti 9e6fc7c7d1 Merge pull request #13 from k9mail/art/update-certificates-in-tests
Update certificates in tests
2014-11-12 16:30:53 +01:00
cketti 0f70d5db40 Merge pull request #12 from k9mail/art/warn-if-not-serializable
Art/warn if not serializable
2014-11-11 19:52:31 +01:00
cketti bd4a236525 Merge pull request #11 from k9mail/art/fix-tests-on-jvm
fix tests on JVM
2014-11-11 19:46:58 +01:00
cketti 7211080415 Merge pull request #10 from k9mail/art/more-tidying-arrays
Replace many arrays with collections
2014-11-11 19:45:35 +01:00
cketti 87ca0d3d2a Use TemporaryAttachmentStore when viewing attachments using file:// URI 2014-11-11 02:11:51 +01:00
cketti 34cfd8e5b4 Extract file related helper functions into separate class 2014-11-11 01:04:39 +01:00
cketti e64ca84f1b Simplify MediaScannerNotifier 2014-11-10 06:43:26 +01:00
cketti c3f1420ef6 Don't open the file after the media scanner added it to the media content provider 2014-11-10 06:43:00 +01:00
cketti dfd40659d1 Append file name to content:// URI
This allows intent filters with patterns for file extensions to match.
2014-11-10 05:46:38 +01:00
cketti 6a1905b7b7 If we can't find an app to view an attachment try again with a file:// URI
Sadly, some apps only support the 'file' scheme in their intent filters. Among
them is Android's own package installer.
2014-11-10 05:37:22 +01:00
cketti 44ecf5d588 Use MIME type used for intent resolution in content provider 2014-11-10 03:12:28 +01:00
cketti f87ab53b9b Try original and inferred MIME type to find best viewer for attachment
In order for Android to find apps that are capable of opening an attachment for
viewing the ACTION_VIEW Intent needs to contain an appropriate MIME type.
Ideally, we'd use the MIME type specified for the attachment in the message.
But often the supplied MIME type is wrong/useless. So we look at the file
extension to try to come up with a sensible MIME type on our own. We then go
on to ask Android which of the two MIME types leads to more apps claiming to
be able to open our attachment for viewing and use that one.
2014-11-10 03:10:09 +01:00
cketti 11a9eff109 Extract intent creation code to method 2014-11-09 20:46:46 +01:00
cketti 6cb3c991db No longer pretend there are attachment types we don't care about 2014-10-20 21:26:18 -04:00
cketti 3c4ad91614 Fix method names/visibility 2014-10-20 21:24:18 -04:00
cketti d3073be89a Rearrange fields/methods 2014-10-20 21:21:09 -04:00
cketti 83d876f246 Inline method 2014-10-20 21:16:31 -04:00
cketti f6822c973d Clean up comments 2014-10-20 21:13:58 -04:00
cketti 1e89314f3e Remove "m" prefix for field names 2014-10-20 21:09:23 -04:00
cketti b3bc85ba10 Split 'populateFromPart' into multiple methods 2014-10-20 21:06:31 -04:00
cketti ca88f59c05 Create named class for loading and displaying attachment thumbnails 2014-10-20 20:46:46 -04:00
cketti 4299eb9771 Rename method and improve documentation 2014-10-20 20:28:38 -04:00
cketti 4a6c52947d Make all fields of AttachmentView private 2014-10-20 20:14:04 -04:00
cketti 0ced8746af Remove old/unused code from click handler 2014-10-20 20:05:21 -04:00
cketti 33d12e4169 Fix russian translation of 'message_view_no_viewer' 2014-10-20 19:52:42 -04:00
cketti 187d760e5f Extract code to display error/status messages to a separate method 2014-10-20 19:50:41 -04:00
cketti dfe1771fcb Fix formatting 2014-10-20 19:39:16 -04:00
cketti b7a8c9b707 Remove unused code 2014-10-20 19:34:16 -04:00
tobiasbaum 3dab8a2ad1 Merge pull request #14 from k9mail/art/tidy-application-ref
remove unnecessary method parameters
2014-10-19 21:53:42 +02:00
Art O Cathain 438a350f55 remove unnecessary method parameters 2014-10-19 18:40:17 +01:00
Art O Cathain a9b0907c31 further simplification 2014-10-12 09:24:08 +01:00
Art O Cathain ba26cfce90 remove controversial methods 2014-10-12 08:54:44 +01:00
Art O Cathain 5dc1b82340 address review comments 2014-10-11 23:52:48 +01:00
Boris Kraut aa959f4457 Get more dependencies from jCenter 2014-10-11 18:03:57 +02:00
Art O Cathain e592aff437 fix test that failed due to certificate expiry 2014-10-11 16:34:58 +01:00
Art O Cathain d980e49fd1 chain the exception 2014-10-11 16:11:12 +01:00
Art O Cathain c6df8f1ba1 warn if not serializable, also add basic unit test 2014-10-11 12:37:36 +01:00
Art O Cathain dd1ec5f47b add unit test 2014-10-11 12:13:07 +01:00
Art O Cathain 668ee71b6c fix tests on JVM 2014-10-11 12:10:07 +01:00
cketti 4aad31e05a Bump version to 5.101 2014-10-10 13:42:52 -04:00
cketti d2bfcab939 Update changelog for 5.101 2014-10-10 13:39:30 -04:00
cketti c3c8221d4a Set minSdkVersion + targetSdkVersion via build.gradle
Without this building with Gradle adds a 'maxSdkVersion' attribute to the manifest. This seems to cause the targetSdkVersion value being ignored. And with a targetSdkVersion value lower than 16 the permissions READ_CONTACTS/WRITE_CONTACTS imply READ_CALL_LOG/WRITE_CALL_LOG. But we don't need/want those permissions.
2014-10-10 13:15:48 -04:00
cketti 7945aab8a7 Merge pull request #9 from k9mail/art/interface-not-impl
use interfaces, not implementions
2014-10-07 23:00:11 -04:00
cketti 0f844fd4d2 Bump version to 5.100 2014-10-07 21:50:15 -04:00
cketti 270160e65a Support reading signing config values from Gradle properties 2014-10-07 21:40:42 -04:00
cketti 87acbb7cac Update changelog for 5.100 2014-10-07 21:30:55 -04:00
cketti 06d1a2471a Change text in feature graphic 2014-10-07 21:27:45 -04:00
Joe Steele a141457886 Revert "Implement SSL file-based session caching"
This reverts commit 43c38a047f.
2014-10-07 21:23:04 -04:00
Art O Cathain 40102d560d Set interface makes more sense for flags 2014-10-05 12:40:35 +01:00
Art O Cathain 195f28db00 Revert accidental change 2014-10-05 12:23:43 +01:00
Art O Cathain 444756839c EnumSet is more efficient than HashSet 2014-10-05 12:08:55 +01:00
Art O Cathain fe7c0ebfac simplify following code review 2014-10-05 11:57:11 +01:00
Art O Cathain 159017e91d removed unneeded variables and clarified names 2014-10-05 11:50:04 +01:00
Art O Cathain d38f21265d use isEmpty instead of size() == 0 for clarify 2014-10-05 10:37:50 +01:00
Art O Cathain 02c0b5f2a3 Use collections instead of arrays to enable stronger typing and reduce cruft 2014-10-05 10:37:36 +01:00
Art O Cathain 203dcfe2c3 use interfaces, not implementions 2014-10-04 12:00:48 +01:00
tobiasbaum dadf5e0865 Merge pull request #7 from artbristol/art/tidying
Art/tidying
2014-09-29 22:15:59 +02:00
Art O Cathain 010d8c9f7e always use import for UrlEncodingHelper 2014-09-29 18:06:21 +01:00
tobiasbaum b8870493cd Merge pull request #6 from tobiasbaum/master
More small refactorings
2014-09-28 20:10:37 +02:00
Art O Cathain 2226ae6a8e fix IDE error 2014-09-28 12:48:46 +01:00
Art O Cathain c438bc1222 remove some more catches 2014-09-28 12:09:34 +01:00
Art O Cathain afb65d5ad7 remove some try-catch cruft 2014-09-28 11:39:32 +01:00
Art O Cathain 46d083bcad fix warnings 2014-09-28 11:19:33 +01:00
Tobias Baum 2be10febf9 Added (still rather shallow) missing Javadocs for classes in BinaryAttachmentBody hierarchy 2014-09-22 21:55:08 +02:00
Tobias Baum 547eb74774 Changed access to mParts in MimeMultipart so that it can be private too 2014-09-22 21:54:02 +02:00
Tobias Baum b6079d6460 UCDetector warnings: Made things private, deleted unused methods and fields 2014-09-22 21:52:59 +02:00
cketti 3b89c6a139 Merge pull request #5 from tobiasbaum/master
added some testcases for MimeMessage parsing + some simple refactorings (2)
2014-09-22 01:32:49 +02:00
zjw f8ffead008 Merge pull request #498 from k9mail/manifest_fixes
Manifest fixes
2014-09-19 10:40:10 -04:00
cketti 8cfd73f6b7 Remove unused strings 2014-09-18 23:41:41 +02:00
cketti ff72a63e17 Remove APG permission 2014-09-18 23:20:53 +02:00
cketti 352fb8fd25 Remove legacy APG interface 2014-09-18 23:20:49 +02:00
cketti 99991e6651 Remove option to select (legacy) APG as crypto provider 2014-09-18 19:15:53 +02:00
cketti 4105cdd3cb Change/fix indentation of Gradle files 2014-09-18 15:18:41 +02:00
cketti 082dd953b2 Remove now unused AccessibleEmailContentActivity 2014-09-18 15:06:00 +02:00
cketti 9d93735d27 Don't "export" BootReceiver's scheduleIntent action 2014-09-18 02:27:11 +02:00
cketti 64cd587b5e Don't export CoreReceiver 2014-09-18 02:25:34 +02:00
cketti 3612d182a1 Tidy up AndroidManifest.xml
Formatting only; no functional changes
2014-09-18 00:43:01 +02:00
Tobias Baum 960d7ba026 Renamed method according to review comment 2014-09-17 20:30:28 +02:00
Tobias Baum 1ea34d2378 Flagged some code with TODOs, added Javadoc for Body 2014-09-14 11:20:18 +02:00
Tobias Baum be954d729c Whitespace, Tabs to Spaces 2014-09-14 11:18:57 +02:00
Tobias Baum 7d32b3d462 Removed unnecessary overriding methods 2014-09-14 11:17:02 +02:00
Tobias Baum a9aa4645af Removed unused methods 2014-09-14 11:15:44 +02:00
Tobias Baum 545dd0db06 Added a getBodyparts method to Multipart so that foreach loops can be used. Removed unnecessary mutators from Multipart. 2014-09-14 11:13:34 +02:00
Tobias Baum 5513d5a99b Pulled down getSize from Part to Message which makes ImapBodyPart superfluous 2014-09-14 11:11:48 +02:00
Tobias Baum d467dca32c Added testcases for parsing and removed unused methods for parsing 2014-09-14 11:05:55 +02:00
cketti 601e2880ac Merge remote-tracking branch 'upstream/master'
Conflicts:
	src/com/fsck/k9/mail/transport/SmtpTransport.java
2014-09-12 07:11:32 +02:00
cketti bd839a995f Merge pull request #3 from frommeyerc/merge-upstream
Extract inner classes from LocalStore
2014-09-12 07:07:07 +02:00
cketti a6fc06f7f9 Fix code style 2014-09-12 06:35:07 +02:00
cketti 3e4beae631 Remove unused 'check mark' color chip 2014-09-12 06:26:03 +02:00
cketti b31660c63e Remove unused strings 2014-09-12 05:54:38 +02:00
cketti 471f1df160 Use checkstyle to monitor code quality 2014-09-12 03:30:34 +02:00
cketti 98559900c2 Add support for disabling pre-dexing 2014-09-12 03:30:13 +02:00
cketti 87a9126107 Update HoloColorPicker to use build tools 20.0.0 2014-09-12 03:30:07 +02:00
cketti a2a9e751e4 Use android-sdk-manager to fetch Android SDK dependencies 2014-09-12 03:28:46 +02:00
Christian Frommeyer 9dba60c997 Some minor code cleanings and logging for LockableDatabase 2014-09-11 20:26:40 +02:00
Christian Frommeyer eced036d69 Extracting Database Setup Schema definition form LocalStore. 2014-09-11 20:26:40 +02:00
Christian Frommeyer 91ef5fa816 Extracted LocalFolder and LocalMessage definition from LocalStore 2014-09-11 20:26:40 +02:00
Christian Frommeyer 89ba2c510b More nested classes extracted from LocalStore. 2014-09-11 20:26:40 +02:00
Christian Frommeyer f92da3af59 Extracting local attachment classes from LocalStore to reduce file size. 2014-09-11 20:26:39 +02:00
Christian Frommeyer 038fceabf0 Move LocalStore to new subpackage to prepare decomposition of nested
classes.
2014-09-11 20:26:39 +02:00
cketti 939b2e3520 Add Play Store feature graphic assets 2014-09-10 21:49:44 +02:00
cketti 5ddfe4b58e Bumped manifest to 4.905 2014-09-10 21:12:12 +02:00
cketti 47cedef85c Update changelog for 4.905 2014-09-10 21:02:48 +02:00
Joe Steele 0f6719387c Re-enable TLSv1.1/1.2 support
Was disabled in 3fd7470d.

Issue 6238.

Related Android change for API 20:
1f63d2c223%5E!/
2014-09-10 11:42:14 -04:00
cketti a10b9ae452 Merge pull request #493 from zjw/ssl_changes
SSL changes
2014-09-10 01:44:44 +02:00
Joe Steele 59a61366ca Merge branch 'remove_gallery_bug_workaround'
Conflicts:
	res/values-it/strings.xml
	res/values-iw/strings.xml
	res/values-sv/strings.xml
2014-09-09 18:59:18 -04:00
Joe Steele 094feced0c Remove unused imports 2014-09-09 18:56:36 -04:00
cketti 2e318096b7 Add new translations
Add new languages that were promised in commit 5a6648ceeb8f2f85f8fae78f547dd0d3350ca1f2:
- Latvian
- Estonian
- Norwegian Bokmål
- Galician (Spain)
2014-09-09 00:59:56 +02:00
cketti 9911208441 Ignore UnusedQuantity and MissingQuantity lint warnings for all translations 2014-09-09 00:10:14 +02:00
cketti 5a6648ceeb Update translations from Transifex
New languages:
- Latvian
- Estonian
- Norwegian Bokmål
- Galician (Spain)
2014-09-09 00:04:10 +02:00
cketti 9e203b75cc Remove gallery bug work-around
This bug was present in the Gallery app shipped with Android 2.0.
The time has come to say good-bye. We will never forget you! But only because you're part of our Git history.
2014-09-07 23:35:18 +02:00
Joe Steele 43c38a047f Implement SSL file-based session caching
Caching is beneficial because it can eliminate redundant cryptographic
computations and network traffic when re-establishing a connection to
the same server, thus saving time and conserving power.
2014-09-06 19:32:06 -04:00
Joe Steele 7dfbd906c9 Eliminate DomainNameChecker
There's no need to maintain our own implementation when comparable
classes already exist in the Android API.

StrictHostnameVerifier is used instead.
2014-09-06 19:32:03 -04:00
Joe Steele 6f14294164 Remove SslHelper. Don't use SecureRandom.
SslHelper has been removed, and its functionality has been transferred
into TrustedSocketFactory.  The added layer of indirection wasn't really
simplifying anything.  It's now easier to see what happens when
createSocket() is invoked.

A new instance of SecureRandom is no longer passed to SSLContext.init().
Instead, null is passed.

The (default) provider of the TLS SSLContext used is OpenSSLProvider,
which provides an SSLSocket instance of type OpenSSLSocketImpl.  The only
use of SecureRandom is in OpenSSLSocketImpl.startHandshake(), where it is
used to seed the OpenSSL PRNG with additional random data.  But if
SecureRandom is null, then /dev/urandom is used for seeding instead.

Meanwhile, the default provider for the SecureRandom service is
OpenSSLRandom, which uses the OpenSSL PRNG as its data source.  So we were
effectively seeding the OpenSSL PRNG with itself.  That's probably okay
(we trust that the OpenSSL PRNG was properly initialized with random data
before first use), but using /dev/urandom would seem like a better source
(or at least as good a source) for the additional seed data added with
each new connection.

Note that our PRNGFixes class replaces the default SecureRandom service
with one whose data source is /dev/urandom for certain vulnerable API
levels anyway.  (It also makes sure that the OpenSSL PRNG is properly
seeded before first use for certain vulnerable API levels.)
2014-09-06 18:15:25 -04:00
cketti 5f0f4e9c21 Merge pull request #485 from haselwarter
Conflicts:
	src/com/fsck/k9/preferences/Settings.java
2014-09-04 23:55:30 +02:00
cketti f5cfaceef4 Increase settings version in preparation of merge into master 2014-09-04 23:54:06 +02:00
cketti 5802e6a36a Remove getNotifyClass() from Folder 2014-09-04 23:54:06 +02:00
cketti 7edd1cb53c Simplify database upgrade when adding 'notify_class' to 'folders' 2014-09-04 23:54:06 +02:00
zjw 8ef45e8f9a Merge pull request #492 from k9mail/remove_unused_resources
Resources cleanup
2014-09-04 17:01:28 -04:00
cketti bd9efa8d01 Whitespace fixes 2014-09-04 22:58:38 +02:00
cketti cb67a21a93 Remove commented-out code 2014-09-04 21:58:18 +02:00
cketti ae8dcc5e8a Fix typo in attribute name 2014-09-04 20:18:05 +02:00
cketti 0c38d4f169 Remove unused resources and merge resource folders 2014-09-04 20:18:05 +02:00
cketti 759fa77c9a Merge pull request #474 from k9mail/tls-client-cert-auth
Client Certificate Authentication
2014-08-30 01:06:28 +02:00
Joe Steele 7c4a684f86 Clean up indentation
White space changes only
2014-08-29 10:47:46 -04:00
Joe Steele 9728609c4c Make the foldable container view INVISIBLE, not GONE
Now when toggling the foldable view, the screen no longer
jumps half a line up or down to recenter itself when
the view is displayed.
2014-08-29 10:47:45 -04:00
Joe Steele 8ade424270 Save/Restore the FoldableLinearLayout state 2014-08-29 10:47:43 -04:00
Joe Steele a756fa3683 Use Theme consistent styles in FoldableLinearLayout
For support of the dark and light themes.

Also:

Redefine mFolded and call it mIsFolded.  Previously,
the view started with mFolded = false (which implies to me
the initial state is unfolded) and yet the view
started in a folded state, which seemed contradictory.

Create updateFoldedState() with code from onClick() (In
preparation for subsequent commit.)
2014-08-29 10:47:41 -04:00
Joe Steele 348fb4dceb Validate client certificate dates 2014-08-29 10:47:39 -04:00
Joe Steele c80634d501 Format log messages with spaces 2014-08-29 09:06:25 -04:00
cketti c2db88d960 Add top margin to the "advanced options" area 2014-08-29 03:43:54 +02:00
cketti 1282e9d461 Remove ldpi resources 2014-08-29 02:41:50 +02:00
cketti 0993e5c57e Merge branch 'remove_obsolete_code'
PR #491 from zjw
2014-08-29 01:55:22 +02:00
cketti ac1ed9eef3 Increment settings version 2014-08-29 01:45:48 +02:00
Dominik Schürmann 05d8fb5e42 Hide client cert option under advanced options dropdown 2014-08-27 11:48:49 +02:00
Dominik Schürmann 2a1733564e Add FoldableLinearLayout for advanced options 2014-08-27 11:30:53 +02:00
Joe Steele e4d26b8c75 Remove code for unsupported API levels
An assortment of miscellaneous changes, each usually limited in scope to a
single file.
2014-08-19 17:17:55 -04:00
Joe Steele bc60c860b8 Remove com.fsck.k9.helper.NotificationBuilder
Only useful on pre-Honeycomb devices.
2014-08-19 17:17:52 -04:00
Joe Steele 786511ed88 Simplify ClipboardManager
No longer API dependent.
2014-08-19 17:17:50 -04:00
Joe Steele 028f6f9055 Remove AccessibleWebView
Only used on pre-ICS devices
2014-08-19 17:17:48 -04:00
Joe Steele 0fba273357 Remove the obsolete "Condensed layout" preference 2014-08-19 17:17:45 -04:00
Joe Steele 8166f03e87 Remove obsolete "Show unread count" preference
Only applies to pre-Honeycomb devices
2014-08-19 17:17:42 -04:00
Joe Steele af77bbd1bc Eliminate obsolete background data sync option
It only applied to pre-ICS devices.

ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED is no longer
broadcast.

ConnectivityManager.getBackgroundDataSetting() always returns true.
2014-08-19 17:14:44 -04:00
Joe Steele c472b89d23 Additional 'Show password' changes after merging master 2014-08-19 16:48:11 -04:00
Joe Steele 1783dd1a63 Merge branch 'master' into tls-client-cert-auth
Conflicts:
	res/layout/account_setup_basics.xml
	res/values/strings.xml
	src/com/fsck/k9/activity/setup/AccountSetupBasics.java
2014-08-19 16:19:02 -04:00
Joe Steele b39f9b95f1 Move initializeViewListeners() to onPostCreate()
Per comments in PR #474

https://github.com/k9mail/k-9/pull/474#commitcomment-7417262
2014-08-19 15:50:32 -04:00
Joe Steele f7fb0cca41 Compare Enum types with ==, !=
Per comments in PR #473

https://github.com/k9mail/k-9/pull/474#commitcomment-7416979

https://github.com/k9mail/k-9/pull/474#commitcomment-7416999
2014-08-18 18:12:39 -04:00
cketti 3bd9e7edf1 Merge pull request #490 from pylerSM/master
Add 'Show password' check box to account setup screen
2014-08-18 22:30:06 +02:00
pylerSM a495627d72 Show password feature 2014-08-18 13:06:25 +02:00
cketti a659393326 Make fields in KeyChainKeyManager final 2014-08-18 05:18:38 +02:00
cketti ac08f520ae Remove KeyChainKeyManager's dependency on 'K9.app' 2014-08-18 05:08:01 +02:00
cketti 4ce2a56b0c Synchronize access to sClientCertificateReferenceWorkaround
Also, refactor for easier readability.
2014-08-18 04:59:57 +02:00
Joe Steele c881207295 Use isFinishing() instead
As suggested by @maniac103
https://github.com/k9mail/k-9/commit/41570e4#commitcomment-7388209
2014-08-14 16:22:51 -04:00
Joe Steele c8f6c4d625 Eliminate searching for '3' in exception message
This was dead code.  The exception message will always start with either
"SMTP response is 0 length" from checkLine() or else "Negative SMTP reply"
from NegativeSmtpReplyException().

The problem originated from way back before 4.904.
2014-08-11 11:08:51 -04:00
Joe Steele 21237c3720 KeyChainKeyManager modifications
The constructor now saves the certificate chain, so the code to retrieve
it again or to perform any additional error checking in
getCertificateChain() is no longer needed.

The constructor now retrieves and saves the private key so that any
resulting errors are detected sooner.

Methods that retrieve the alias perform checks to assure that the client
cert. satisfies the requested issuers and key type.  It's known that
Sendmail may provide a list of issuers in its certificate request, but
then may authenticate against a much larger set of CAs, but then later
reject the mail because the client certificate was not acceptable.
Vetting against the issuer list helps detect such certificate problems
sooner (upon connection) rather than later (upon transmission of mail).
Earlier error detection is necessary so that errors may be presented to
the user during account setup.

Portions of these modifications are based on code from KeyManagerImpl:
https://android.googlesource.com/platform/external/conscrypt/+/master/src/main/java/org/conscrypt/KeyManagerImpl.java
2014-08-11 11:08:26 -04:00
Joe Steele 2b05f90d4d Move KeyChainKeyManager
Move KeyChainKeyManager to com.fsck.k9.net.ssl because it is used by
SslHelper and because the class extends X509ExtendedKeyManager, which is
in javax.net.ssl.
2014-08-11 11:08:24 -04:00
Joe Steele c5085be2ca Restore view visibility based on restored CheckBox state
The problem can be observed if, when modifying the outgoing server
settings, you change the state of the mRequireLoginView check box,
then change the screen orientation.

This is necessary because the OnCheckChanged listener (which
normally updates the view visibility) is not yet set. (The listeners
are set up after view initialization so that they only fire on
user input.)
2014-08-11 11:08:22 -04:00
Joe Steele 346d903ec3 Only trigger certificate chooser on user input
It should not be triggered when the instance state is restored
with an AuthType spinner selection of EXTERNAL.

The logic here for the AuthType spinner is similar to that of
the parent commit for the SecurityType spinner.
2014-08-11 11:08:20 -04:00
Joe Steele 5d5fab3081 Fix default port setting reversion
The problem:  begin modifying the server settings by changing the security
type (which will change the port to a default value), then change the port
to a custom value, then change screen orientation.  The default port value
is restored, wiping out the custom value.

When onRestoreInstanceState() is called, the custom port value is
restored.  But the spinner doesn't actually restore its state at that
time.  Instead, it waits until View.layout(), at which time it posts a
runnable to call onItemSelected() if the restored state doesn't match the
state initialized in onCreate().  When onItemSelected() is eventually run
sometime later, it wipes out the custom port value that was restored.

The solution is to keep track of the spinner state ourselves and only
revert the port to a default when we see the spinner state changed by the
user.

This problem goes back to 4.904 and before.
2014-08-11 11:07:58 -04:00
Joe Steele cf3561da5c Trigger certificate chooser when check box checked
For convenience.  Implemented in onCheckChanged().

As a consequence, onCheckChanged() must not be triggered when the instance
state is restored (would occur if the check box state was checked when
saved), otherwise the certificate chooser would pop up once the state was
restored.  Therefore, all listeners have been moved into
initializeViewListeners() which is invoked after the state has been
restored.

Because onCheckChanged() is no longer triggered in
onRestoreInstanceState(), updateViewVisibility() was implemented to
restore the view visibility.
2014-08-11 11:07:56 -04:00
Joe Steele 301ac48a38 Throw CertificateValidationException if EXTERNAL authentication fails
This is done when the SASL EXTERNAL mechanism isn't advertised (indicating
the possibility that the server did not accept the client certificate) or
when the command for authenticating with SASL EXTERNAL fails.

The CertificateValidationException will trigger a notification to the user
that there's an authentication problem that needs addressing.

Also, there were instances where CertificateValidationException was being
thrown with a new CertificateException as the cause for the purpose of
notifying the user when STARTTLS is not available.  This has been slightly
simplified by eliminating the need to include a new CertificateException
as a cause.
2014-08-11 11:07:54 -04:00
Joe Steele b557ba008c Implement SMTP AUTH EXTERNAL
Also, simplify by using Utility.base64Encode(String) in lieu of
new String(Base64.encodeBase64(String.getBytes())
2014-08-11 11:07:53 -04:00
Joe Steele c0be0eea12 Use the correct POP3 AUTH command 2014-08-11 11:07:51 -04:00
Joe Steele fe033e014f Avoid setting conflict warning when SMTP login not required 2014-08-11 11:07:49 -04:00
Joe Steele 65144e3759 Handle client certificate errors
If the alias is empty or null, don't bother using KeyChainKeyManager.

If the alias is not empty, confirm that it is associated with a
certificate, otherwise throw a CertificateValidationException
which will notify the user of the problem and ask the user to
check the server settings.

Likewise, the user is notified if the client certificate was
not accepted by the server.
2014-08-11 11:07:48 -04:00
Joe Steele 231f3645f9 Trigger certificate chooser on authentication change
If the user chooses client certificate authentication,
immediately pop up the certificate chooser.

If the user chooses password authentication, move the focus to the
password View.
2014-08-11 11:07:46 -04:00
Joe Steele 21cc3d9176 Remove ClientCertificateRequiredException
With this commit, KeyChainKeyManager no longer throws the exception and
AccountSetupCheckSettings no longer catches it.

It was being thrown when the server requested a client certificate but no
client certificate alias had been configured for the server.

The code was making the incorrect assumption that the server would only
request a client certificate when such a certificate was *required*.
However, servers can be configured to accept multiple forms of
authentication, including both password authentication and client
certificate authentication.  So a server may request a certificate without
requiring it.  If a user has not configured a client certificate, then
that should not be treated as an error because the configuration may be
valid and the server may accept it.

The only indication that a certificate is *required* is when a
SSLProtocolException is thrown, caused by a SSLHandshakeException
resulting from a fatal handshake alert message received from the server.
Unfortunately, such a message is fairly generic and only "indicates that
the sender was unable to negotiate an acceptable set of security
parameters given the options available."  So there is no definitive way to
know that a client certificate is required.

Also, KeyChainKeyManager.getCertificateChain() and getPrivateKey() no
longer throw IllegalStateException().  These methods are permitted to
return null, and such a response is appropriate if the user has deleted
client certificates from the device.  Again, this may or may not cause the
server to abort the connection, depending on whether the server *requires*
a client certificate.
2014-08-11 11:07:44 -04:00
Joe Steele fa853f7e1d Remove SslHelper.isClientCertificateSupportAvailable()
The app's minSdkVersion = 15 (Android 4.0.3, Ice Cream Sandwich MR1),
so there's no need to test the API level.

This also removes '@SuppressLint("TrulyRandom")'.  I find no
documentation for it, nor do I find any additional lint errors
with its removal.
2014-08-11 11:07:42 -04:00
Joe Steele b10b13b865 Force manual setup if using client certificates
Presently, auto-setup doesn't support certificates,
resulting in "Cannot connect to server. (Error
while decoding store URI)".
2014-08-11 11:07:41 -04:00
Joe Steele 0224a89109 Use theme based style 2014-08-11 11:07:38 -04:00
Joe Steele 88016ae52e Revert unused code changes 2014-08-11 11:07:37 -04:00
Joe Steele 38e9af5320 Don't clear widgets when removing
The user may toggle the checkbox, and then decide to toggle it again.

This also fixes a problem when restoring the activity state.  When the
checkbox was restored as checked, the listener was firing and wiping the
the restored alias.
2014-08-11 11:07:35 -04:00
Joe Steele acab554ee5 Save/Restore activity state
This assures that changes made to the port setting and to the chosen
client certificate are saved and restored.
2014-08-11 11:07:34 -04:00
Joe Steele 2e981e0c7d Don't trigger validateFields() except on user input
Previously, with settings of Security=SSL and authentication=certificate,
attempting to change Security=None would (of course) be blocked.  So
Security would remain SSL.  But the authentication options would then
include "Password, transmitted insecurely", whereas the option should
have remained as "Normal password" (because the security remained SSL).

The problem could have been fixed with a simple shuffling in
updatePortFromSecurityType() so that updateAuthPlainTextFromSecurityType()
was invoked before mPortView.setText(), but the logic for requiring that
ordering was not plain to see.  (Although no longer necessary, the
shuffling was done as well.)
2014-08-11 11:06:50 -04:00
Joe Steele c861b27df8 Fix outgoing server settings display
Add test for username == "".

Without it, the mRequireLoginView remains checked, and the empty username
and password boxes are not hidden.

The problem occurs if importing an SMTP server that has an
authenticationType, but no username or password (i.e., no authentication
required).

That's the way settings were exported in 4.904 and before.
2014-08-11 11:06:48 -04:00
Joe Steele 373c7569ed Fix password prompts on account import
Don't prompt if server's AuthType == EXTERNAL

Don't prompt for SMTP servers that don't require authentication
(no user name).
2014-08-11 11:06:46 -04:00
Joe Steele 6d8497a3c3 Fix WebDAV manual account setup
When creating a new account, the Incoming Server Settings screen
was initialized with the user name = PLAIN.
2014-08-11 11:06:45 -04:00
Joe Steele bef10812d3 Fix so WebDAV does not have STARTTLS auth. type option 2014-08-11 11:06:43 -04:00
Joe Steele eb68bc0a9d Avoid NPE on export
Occurs when the SMTP server doesn't require authentication
(authenticationType == null).

The javadoc for ServerSettings says that both authenticationType and
connectionSecurity may be null.
2014-08-11 11:06:42 -04:00
Joe Steele 34fd6d3ea7 Import/Export client certificate alias setting 2014-08-11 11:06:40 -04:00
Joe Steele ada74db8d5 Refactor KeyChainKeyManager()'s sClientCertificateReferenceWorkaround
The referenced issue states that it is only applicable to Android < 4.2
(testing confirms the problem on 4.1.2, but not on 4.2.2).

A test was added for the version code, primarily as a finder's aid for a
day when K-9 Mail no longer supports Android < 4.2 and the work-around can
be removed.

The referenced issue also states that it is only necessary to hold a
reference to the first PrivateKey retrieved. (Testing indicates that the
problem is avoided so long at least one reference is always maintained to
a PrivateKey -- it doesn't actually need to be a continuous reference to
the first PrivateKey.)

From my understanding, a normal class loader never unloads a class, so the
static reference can be safely kept privately in KeyChainKeyManager.
2014-08-11 11:06:38 -04:00
Joe Steele 51829a2451 Reorganize the server setup layouts
This only changes the vertical display order of the widgets.

The user will likely review the settings from top to bottom, but
the way they were previously arranged, settings lower on the list
were affecting things higher on the list.

Generally show top to bottom:
Server
security type
port (affected by security type above)
require login checkbox (SMTP only, affects everything below)
user
auth. type (affected by security type above, affects everything below)
password (affected by auth. type above)
client cert (affected by auth. type above)
2014-08-11 11:05:18 -04:00
Philipp Haselwarter 7aa4c1308e Configure mail notifications by folder class
In response to Issue 1794,
- add a configuration option in the account preferences to show
  notifications only for 1st/2nd/etc class folders
- add an option in the folder preferences to set the notification class
  as 1st, 2nd or inherited from the folder's push class

The default behaviour remains unchanged.
2014-08-06 11:04:07 +02:00
Joe Steele 41570e4305 Fix leaked window error in FolderList
Observed after wiping data and then tapping a launcher shortcut
for an account.
2014-07-30 19:26:43 -04:00
Joe Steele fe49a5f005 Avoid NPE in MessageOpenPgpView.handleError()
E/AndroidRuntime(25655): FATAL EXCEPTION: main
E/AndroidRuntime(25655): Process: com.fsck.k9, PID: 25655
E/AndroidRuntime(25655): java.lang.NullPointerException
E/AndroidRuntime(25655):     at com.fsck.k9.view.MessageOpenPgpView.handleError(MessageOpenPgpView.java:385)
E/AndroidRuntime(25655):     at com.fsck.k9.view.MessageOpenPgpView.access$3(MessageOpenPgpView.java:384)
E/AndroidRuntime(25655):     at com.fsck.k9.view.MessageOpenPgpView$DecryptVerifyCallback.onReturn(MessageOpenPgpView.java:357)
E/AndroidRuntime(25655):     at org.openintents.openpgp.util.OpenPgpApi$OpenPgpAsyncTask.onPostExecute(OpenPgpApi.java:195)
E/AndroidRuntime(25655):     at org.openintents.openpgp.util.OpenPgpApi$OpenPgpAsyncTask.onPostExecute(OpenPgpApi.java:1)
E/AndroidRuntime(25655):     at android.os.AsyncTask.finish(AsyncTask.java:632)
E/AndroidRuntime(25655):     at android.os.AsyncTask.access$600(AsyncTask.java:177)
E/AndroidRuntime(25655):     at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:645)
E/AndroidRuntime(25655):     at android.os.Handler.dispatchMessage(Handler.java:102)
E/AndroidRuntime(25655):     at android.os.Looper.loop(Looper.java:136)
E/AndroidRuntime(25655):     at android.app.ActivityThread.main(ActivityThread.java:5128)
E/AndroidRuntime(25655):     at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(25655):     at java.lang.reflect.Method.invoke(Method.java:515)
E/AndroidRuntime(25655):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:795)
E/AndroidRuntime(25655):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:611)
E/AndroidRuntime(25655):     at dalvik.system.NativeStart.main(Native Method)
2014-07-30 19:26:41 -04:00
Joe Steele 710e7a4430 Notify user of invalid account-setup settings combo
Specifically, warn and block them when attempting to configure Client
Certificate Authentication in combination with Connection Security = None.

If this were not made obvious to the user, they might not understand why
they are not permitted to tap "Next".

Also, move the initialization of all view listeners out of onCreate() into
initializeViewListeners() which is then called near the end of onCreate(),
helping to assure that the listeners won't be triggered during the
initialization of views inside onCreate().
2014-07-28 22:55:34 -04:00
cketti c36d2d7a5e Use latest Gradle Android plugin and build tools 2014-07-26 18:06:20 +02:00
cketti 96be55a262 Merge pull request #481
Sort apple emails in own category, add apple.com and icloud.com
2014-07-26 16:56:47 +02:00
cketti f463aa9fa0 Fix indentation 2014-07-26 16:53:03 +02:00
miguelpinheiro a671e51052 Sort apple emails in own category and add icloud.com 2014-07-19 15:48:44 +01:00
miguelpinheiro 2486caaf2d Sort apple emails in own category and add icloud.com 2014-07-19 15:46:11 +01:00
miguelpinheiro 14032088db Update providers.xml
Added apple mail providers (apple.com, mac.com, me.com, icloud.com)
2014-07-19 15:22:51 +01:00
Joe Steele 3c025379d4 Rework validateFields() and updateViewFromAuthType()
Previously, it was possible to have "Require sign-in" unchecked and a
"Security = None" setting for the outgoing server and still not be able to
tap "Next" because of a hidden (and irrelevant) "Authentication = Client
certificate" setting.

Check that the user has actually chosen a client certificate in
AccountSetupOutgoing.validateFields().

Also, there's no need to clear the password and certificate fields when
hiding them.  The user may accidentally change settings and want to change
them back without wiping out the existing settings.
2014-07-13 16:02:21 -04:00
Joe Steele 008891a375 Clean up indentation
White space changes only.
2014-07-13 16:01:51 -04:00
Ashley Willis 0a507463c4 Merge pull request #478 from typingArtist/master
Fix: HELO/EHLO with literal IPv6 address not conformant to RFC5321
verified in spec – ashleywillis
2014-07-05 13:12:38 -04:00
Matthias Wächter acd756e642 Fixed https://github.com/typingArtist/k-9/issues/1. HELO/EHLO IPv6 address literals
are now conforming to RFC5321.
2014-07-05 18:59:48 +02:00
Joe Steele cf718780f6 Fixes needed after merging in master
Also, fix unit tests.
2014-07-04 19:23:43 -04:00
Joe Steele 3142a9a225 Merge branch 'master' into tls-client-cert-auth
Conflicts:
	src/com/fsck/k9/fragment/ConfirmationDialogFragment.java
2014-07-04 18:08:07 -04:00
cketti 731da2747e Merge pull request #476 from k9mail/remove_actionbarsherlock
Remove ActionBarSherlock
2014-06-28 03:41:23 +02:00
Koji Arai 750e4a7808 Updated Japanese translation of the changelog. 2014-06-22 11:03:22 +09:00
cketti ba69b3a647 Remove ActionBarSherlock 2014-06-21 17:09:45 +02:00
cketti 103f85aa01 Fix 'testsOnJVM' Gradle task 2014-06-21 15:33:45 +02:00
cketti 324db56569 Use latest Gradle Android plugin and build tools 2014-06-14 03:29:44 +02:00
cketti 59bda357e0 Merge pull request #472 from mikeperry-tor/improve-header-privacy
Improve header privacy (Issues 6372, 3559, and 4690)
2014-06-14 03:02:49 +02:00
Mike Perry d67a59b77f Fixes for cketti's code review on pull req #472
Leave the hostname == null checks so we can fall back if a hostname is not
found. Also convert message-id to upper case to match Apple Mail (for
privacy).
2014-06-13 17:49:26 -07:00
Dominik Schürmann aad171ff7e Client Certificate Authentication 2014-06-05 21:03:18 +02:00
Mike Perry 38c90146e1 Issue 6372: Add preference for setting timezone to UTC.
This is a privacy preference to avoid leaking your current location while
replying to email.
2014-05-27 07:53:50 -07:00
Mike Perry 87802a01ef Issue 4690: Add privacy pref to omit K-9 User-Agent header. 2014-05-27 07:53:18 -07:00
Mike Perry 7ac7fe2cfe Issue 3559: Use From or ReplyTo hostname in Message-ID if available.
I wrote this fix to avoid obviously specifying that I am using a mobile device
to reply to an email.

Others want this for ease of filtering messages from their host by Message-ID.
2014-05-27 07:51:23 -07:00
cketti 2fdcb77c5c Merge pull request #470 from k9mail/issue_6260_delete_confirmation_in_message_list
Add support for delete confirmation in message list
2014-05-22 12:46:28 +02:00
cketti 082e13df26 Add support for delete confirmation in message list
Fixes issue 6260
2014-05-18 20:21:33 +02:00
cketti 95f33c38fe Merge pull request #468
Extract a TextBodyBuilder class

Conflicts:
	src/com/fsck/k9/activity/MessageCompose.java
2014-05-04 03:54:10 +02:00
Joe Steele ed3fcf375a Eliminate unused variable & redundant call
There are no side effects.

The subsequent executeOpenPgpMethod() calls
getOpenPgpInputStream() which calls
buildText(false)
2014-05-03 12:58:46 -04:00
Joe Steele 00a60a0f4f Clean up lint items
Eliminate unused variables/fields/imports
2014-05-03 12:51:37 -04:00
Joe Steele 523180020c Don't use enum ordinal as array index, v. 2
This revises commit 51aa34d per the GitHub comments therewith.
2014-05-03 11:44:21 -04:00
Joe Steele 58efee8be2 Avoid StrictMode error in OpenPgpApi
E/StrictMode(9278): A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.
E/StrictMode(9278): java.lang.Throwable: Explicit termination method 'close' not called
E/StrictMode(9278): 	at dalvik.system.CloseGuard.open(CloseGuard.java:184)
E/StrictMode(9278): 	at android.os.ParcelFileDescriptor.<init>(ParcelFileDescriptor.java:179)
E/StrictMode(9278): 	at android.os.ParcelFileDescriptor.<init>(ParcelFileDescriptor.java:168)
E/StrictMode(9278): 	at android.os.ParcelFileDescriptor.createPipe(ParcelFileDescriptor.java:362)
E/StrictMode(9278): 	at org.openintents.openpgp.util.ParcelFileDescriptorUtil.pipeFrom(ParcelFileDescriptorUtil.java:34)
E/StrictMode(9278): 	at org.openintents.openpgp.util.OpenPgpApi.executeApi(OpenPgpApi.java:222)
E/StrictMode(9278): 	at org.openintents.openpgp.util.OpenPgpApi$OpenPgpAsyncTask.doInBackground(OpenPgpApi.java:189)
E/StrictMode(9278): 	at org.openintents.openpgp.util.OpenPgpApi$OpenPgpAsyncTask.doInBackground(OpenPgpApi.java:1)
E/StrictMode(9278): 	at android.os.AsyncTask$2.call(AsyncTask.java:288)
E/StrictMode(9278): 	at java.util.concurrent.FutureTask.run(FutureTask.java:237)
E/StrictMode(9278): 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
E/StrictMode(9278): 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
E/StrictMode(9278): 	at java.lang.Thread.run(Thread.java:841)
2014-05-03 11:43:45 -04:00
Joe Steele 5cc4d7c6a9 Avoid NPE in MessageOpenPgpView.updateLayout()
E/AndroidRuntime(24914): FATAL EXCEPTION: main
E/AndroidRuntime(24914): Process: com.fsck.k9, PID: 24914
E/AndroidRuntime(24914): java.lang.NullPointerException
E/AndroidRuntime(24914):    at org.openintents.openpgp.util.OpenPgpServiceConnection.<init>(OpenPgpServiceConnection.java:35)
E/AndroidRuntime(24914):    at com.fsck.k9.view.MessageOpenPgpView.updateLayout(MessageOpenPgpView.java:115)
E/AndroidRuntime(24914):    at com.fsck.k9.view.SingleMessageView.setMessage(SingleMessageView.java:623)
E/AndroidRuntime(24914):    at com.fsck.k9.fragment.MessageViewFragment$Listener$2.run(MessageViewFragment.java:602)
E/AndroidRuntime(24914):    at android.os.Handler.handleCallback(Handler.java:733)
E/AndroidRuntime(24914):    at android.os.Handler.dispatchMessage(Handler.java:95)
E/AndroidRuntime(24914):    at android.os.Looper.loop(Looper.java:136)
E/AndroidRuntime(24914):    at android.app.ActivityThread.main(ActivityThread.java:5081)
E/AndroidRuntime(24914):    at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(24914):    at java.lang.reflect.Method.invoke(Method.java:515)
E/AndroidRuntime(24914):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
E/AndroidRuntime(24914):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:607)
E/AndroidRuntime(24914):    at dalvik.system.NativeStart.main(Native Method)
2014-05-03 11:43:05 -04:00
Joe Steele f17b45c152 Provide Eclipse .project file for new plugin
This gives the openpgp-api-library an Eclipse
project name consistent with the names used for
the other plugins.  This helps with initial
project setup and avoids project naming collisions.
2014-05-03 10:38:06 -04:00
Joe Steele e0ce5fc4ce Eliminate superclass callbacks
Service components don't need to call
super.onCreate() and super.onDestroy()
2014-05-03 10:36:36 -04:00
cketti e2cffa074f Update/remove comments 2014-05-03 06:33:33 +02:00
cketti a9cfa9ae68 Code style
No functional changes
2014-05-03 06:13:38 +02:00
Koji Arai 3a02bfb0a9 I have deleted a change in 0323af0. 2014-05-03 06:12:56 +02:00
Koji Arai 9c5b9cce90 Adapt to the astyle version 2.04.
* max-instatement-indent:
The valid value is 40 thru 120.

* brackets=attach:
We should change the brackets options to the style option in v2.02 or later.

For more detail, see the http://astyle.sourceforge.net/news.html.
2014-05-02 14:31:13 +09:00
Koji Arai 0a09060ed7 adapt to product code 2014-05-02 13:42:05 +09:00
Koji Arai 215ada2e77 astyle 2014-05-02 13:21:35 +09:00
Koji Arai 4260dc75d3 more refactor about quoted text 2014-05-02 10:45:20 +09:00
Koji Arai 68850b1dc9 Use setter for quoted text like signature 2014-05-02 09:43:14 +09:00
Koji Arai 71e766999c adapt to product code 2014-05-02 09:11:31 +09:00
Koji Arai e163fb87e1 set signature parameter when it is only necessary 2014-05-02 09:06:23 +09:00
Koji Arai 00fb83e1b3 fixup! Use local variable for more readability 2014-05-02 08:49:01 +09:00
Koji Arai 7fd52a735d fixup! Rename mDraft to mInsertSeparator. 2014-05-02 08:44:19 +09:00
Koji Arai 421879e9cf Remove unused getter 2014-05-02 08:39:07 +09:00
Koji Arai 56a48476e4 Rename mDraft to mInsertSeparator. 2014-05-02 08:36:46 +09:00
Koji Arai a08687b70e Use local variable for more readability. 2014-05-02 08:28:50 +09:00
Koji Arai 6155a65f65 Refactoring: extract a class TextBodyBuilder 2014-05-02 01:33:39 +09:00
Koji Arai 9a99c77653 Add a gradle task testsOnJVM. 2014-04-29 21:48:51 +09:00
Koji Arai 59ae1d034c Add build.gradle for tests-on-jvm 2014-04-29 15:35:29 +09:00
Koji Arai 615a1ae9a7 Remove duplicate code. use Utility.hasConnectivity() 2014-04-28 15:36:07 +09:00
cketti 51aa34d52b Don't use enum ordinal as array index
This fixes a crash when setting up WebDAV accounts.
2014-04-27 03:29:06 +02:00
cketti da74253f7b Bumped manifest to 4.904 2014-04-18 16:55:41 +02:00
cketti 818aed5f8c Update changelog for 4.904 2014-04-18 16:53:40 +02:00
cketti e1d9c60b83 Merge pull request #467 from grote/master
Add information for MyKolab.com and its variants to providers
2014-04-15 01:42:34 +02:00
Torsten Grote 79ae191c2e add information for MyKolab.com and its variants to providers 2014-04-14 16:03:36 +02:00
cketti 8e078bc014 Merge pull request #466 from thi/fix-apg-legacy-encryption
Fix APG legacy encryption
2014-04-09 20:28:19 +02:00
Thialfihar b765988423 Fix APG legacy encryption 2014-04-09 19:45:37 +02:00
cketti 5640dece0f Merge pull request #462 from open-keychain/openpgp
OpenPGP: Hide lookup key button explicitly if not needed
2014-04-08 22:18:18 +02:00
cketti 3054ff757b Update translations to use ellipsis character 2014-04-08 21:29:40 +02:00
cketti d6a9b4e4d4 Remove obsolete layout attributes
Fixes more Lint warnings
2014-04-08 21:27:11 +02:00
cketti c597d63ae6 Fix more Lint warnings 2014-04-08 21:03:33 +02:00
cketti 6924d68376 Merge branch 'translations_from_transifex'
This has the potential to break stuff. Please report all translation-related
oddities.
2014-04-08 18:07:14 +02:00
cketti 6267f1249b Add script to fix up the files we pull from Transifex 2014-04-08 18:05:04 +02:00
cketti 420e6a91be Remove scripts to modify translations for Gradle support 2014-04-08 18:02:47 +02:00
cketti 1b14472a59 Remove scripts to sync translations
Now that we're using Transifex  for translations we no longer need
those scripts.
2014-04-08 18:00:02 +02:00
cketti a7e157eac7 Remove Canadian French translation
It looks like this translation hasn't been maintained for quite some
time.
2014-04-08 17:46:45 +02:00
cketti d5c6d96112 Fetch translations from Transifex
Include new Basque (eu) translation
2014-04-08 17:36:20 +02:00
cketti c4d930f326 Remove newlines that mess up Transifex 2014-04-08 05:13:39 +02:00
cketti 24cdf811e6 Prepare to integrate Transifex translations
Change the translated strings files to a format similar to what
Transifex exports. This should make it easier to see the content changes
when translations are pulled from Transifex for the first time.
2014-04-08 04:35:21 +02:00
cketti eaa12d2bd4 Sync translations (and remove place holders) 2014-04-08 04:35:21 +02:00
cketti 13bc441c7b Remove and rearrange comments to better work with Transifex 2014-04-08 04:35:05 +02:00
cketti 95cc319101 Merge branch 'fix_lint_warnings'
Fix some lint warnings
2014-04-07 20:41:20 +02:00
cketti 510195bce7 Explicitly use the default locale 2014-04-07 20:35:16 +02:00
cketti efc5565b91 Extract hardcoded string from layout 2014-04-07 20:24:01 +02:00
cketti ebed217c13 Add missing inputType attribute 2014-04-07 20:19:51 +02:00
cketti 5c59b25367 Fix (bad) manual boxing
Fixes UseValueOf lint warning
2014-04-07 20:12:46 +02:00
cketti 7e040ea84c Fix SpUsage lint warnings 2014-04-07 20:09:22 +02:00
cketti 3da2ef7fbe Fix PxUsage warning 2014-04-07 20:00:53 +02:00
cketti 8b93d37b39 Suppress warning about "inlined API" 2014-04-07 20:00:14 +02:00
cketti 136bdbc483 Fix whitespace to get rid of ExtraText lint warnings 2014-04-07 19:56:08 +02:00
cketti 0526ddd2aa Remove unused resources 2014-04-07 19:52:37 +02:00
cketti 114d72da3c Remove deprecated resource attributes 2014-04-07 19:31:10 +02:00
cketti ab72aa0dd7 Don't reuse IDs 2014-04-07 19:17:50 +02:00
cketti c53973910f View class referenced from a layout should be public 2014-04-07 18:53:47 +02:00
cketti 19b808b93b Don't use hardcoded package in namespace 2014-04-07 18:50:30 +02:00
cketti 4df53080d3 Ignore missing translations 2014-04-07 18:48:57 +02:00
cketti 945e539341 Remove some unused strings 2014-04-07 18:24:22 +02:00
cketti 857c72d691 Fix typo 2014-04-07 18:17:23 +02:00
cketti 088549ab62 Merge pull request #465 from jca02266/master
Changed for Android Gradle plugin 0.9
2014-04-07 18:15:40 +02:00
cketti 6a6e9979e2 Fix potential ClassCastException
Implemented the fix suggested by zjw in pull request #463
https://github.com/k9mail/k-9/pull/463

Fixes issue 5928
2014-04-07 17:46:39 +02:00
Koji Arai e0065ce014 Changed for Android Gradle plugin 0.9
see http://tools.android.com/tech-docs/new-build-system/migrating_to_09
2014-04-06 16:38:22 +09:00
cketti 16df038157 Merge pull request #464 from jca02266/master
Should match the buildToolsVersion with other build.gradle
2014-04-05 17:36:00 +02:00
Koji Arai 929a61c035 Should match the buildToolsVersion with other build.gradle 2014-04-05 12:57:28 +09:00
Dominik Schürmann c36ef88e64 Hide lookup key button explicitly, previously it was still visible after downloading a key 2014-04-03 15:42:57 +02:00
cketti 16ec0337d1 Fix building with ant 2014-04-01 02:50:12 +02:00
cketti 49dbaf034c Try to use the correct identity with OpenPGP API's EXTRA_ACCOUNT_NAME 2014-04-01 02:44:47 +02:00
cketti cc8353d255 Merge pull request #457 from openpgp-keychain/openpgp
OpenPGP Provider API
2014-04-01 02:44:32 +02:00
Dominik Schürmann 6175c4c72d Use identity instead of account in MessageCompose, simplify account naming 2014-04-01 00:16:14 +02:00
Dominik Schürmann 71a8ffc2b5 Parcelable versioning, API_VERSION=3 2014-03-30 19:20:46 +02:00
Dominik Schürmann 3fb9cddb33 Fix gradle build 2014-03-26 23:05:45 +01:00
Dominik Schürmann 974a73b07d Use new account extra to allow multiple accounts 2014-03-26 22:11:19 +01:00
Dominik Schürmann 8f1723a451 Update openpgp library 2014-03-26 21:48:43 +01:00
cketti 5c93f105ea Avoid NullPointerException reported via Google Play 2014-03-23 00:39:10 +01:00
Joe Steele bd4b7d3664 Issue 6280 -- SMTP Setup: ArrayIndexOutOfBoundsException: length=0; index=0
When the outgoing server settings don't require
authentication, userInfoParts.length == 0.
2014-03-20 10:56:02 -04:00
Joe Steele 95f62785fc Eliminate unused field/parameter 2014-03-20 09:47:43 -04:00
cketti 7e3ae3ca3d Merge pull request #458 from rtreffer/patch-2
Prevent a "Resource.NotFoundException" when building from AOSP tree
2014-03-19 23:09:40 +01:00
Rene Treffer b17890251d Prevent a "Resource.NotFoundException"
AAPT is a bit too aggressive per default and will kill some needed resources (e.g. forward mail icon). Prevent AAPT from optimizing too much.
2014-03-19 23:06:32 +01:00
Joe Steele b490773546 Include send failures in the K9mail-errors folder 2014-03-11 19:22:36 -04:00
Joe Steele 5162d847ad Build plugins and tests with SDK 19 2014-03-11 19:10:25 -04:00
Joe Steele 01d2247ffd Change POP3 error response detection
Instead of interpreting a "-" at the beginning of a line as
an error response, consider the absence of a "+" at the
beginning of a line as an error response.

This is what Thunderbird does.

http://hg.mozilla.org/releases/comm-esr24/file/55e96a433bd1/mailnews/local/src/nsPop3Protocol.cpp#l1177

The problem arises with godaddy servers spewing additional
lines of data upon login failure.  The login was being
interpreted as successful, and a STAT commanded was subsequently
being sent, resulting in a dialog saying 'Cannot connect to
server. (Invalid int: "auth_error:")'.

$ openssl s_client -quiet -crlf -connect pop.secureserver.net:995
...
+OK <24984.1394317012@pop.secureserver.net>
user testuser
+OK
pass testpass
testuser not found in the auth database
warning: auth_error: authorization failed (no such object)
-ERR authorization failed  Check your server settings.
2014-03-11 19:10:14 -04:00
Joe Steele dc920b8641 Use the mApplication field in lieu of K9.app 2014-03-11 19:08:49 -04:00
Joe Steele e475e51731 Rework handling of certificate errors while pushing
Eliminate import of MessagingController in ImapStore.
2014-03-11 19:08:09 -04:00
Joe Steele a7898fa2eb Fix issue 6269: IMAP LOGIN failure
Some IMAP servers are broken and don't correctly handle string
literals with the LOGIN command.

This switches to using quoted strings instead.

This is what Thunderbird does.
2014-03-11 19:06:00 -04:00
cketti 18da76f4aa Increase number of values available for 'local folder size'
Fixes issue 6235
2014-03-09 05:49:11 +01:00
cketti c2abfbe165 Exclude error folder from unread/starred count 2014-03-09 05:00:42 +01:00
cketti e55feee952 Use latest Gradle Android plugin and build tools 2014-03-09 00:00:58 +01:00
Dominik Schürmann fba406c29f Fix compilation with new lib 2014-03-07 23:34:23 +01:00
Dominik Schürmann 862d1267a8 rename library to avoid confusion and remove OpenKeychain specific intents 2014-03-07 23:07:11 +01:00
Dominik Schürmann db62215eca Update OpenPgpApi 2014-03-07 11:25:24 +01:00
Dominik Schürmann 949f8ae47e Fix documentation and variable naming 2014-03-06 13:58:06 +01:00
Dominik Schürmann 0f81cc192a Revert accedentially added signing options from build.gradle 2014-03-04 22:14:47 +01:00
Dominik Schürmann 6881daae6a Remove debug logs 2014-03-04 22:11:42 +01:00
Dominik Schürmann f5b1ed920b Handle SIGNATURE_SUCCESS_UNCERTIFIED, code cleanup 2014-03-04 15:10:20 +01:00
Dominik Schürmann 4a69ef1509 New OpenPGP Provider API 2014-03-02 16:51:47 +01:00
1819 changed files with 60912 additions and 92768 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
*.sh text eol=lf
gradlew text eol=lf

View File

@ -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>

View File

@ -3,6 +3,6 @@ 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 = res/values-<lang>/strings.xml
source_file = res/values/strings.xml
file_filter = k9mail/src/main/res/values-<lang>/strings.xml
source_file = k9mail/src/main/res/values/strings.xml
source_lang = en

View File

@ -1,48 +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_STATIC_JAVA_LIBRARIES += android-support-v4
LOCAL_MODULE_TAGS := eng
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, plugins/ActionBarSherlock/library/src)
LOCAL_SRC_FILES += $(call all-java-files-under, plugins/Android-PullToRefresh/library/src)
LOCAL_SRC_FILES += $(call all-java-files-under, plugins/ckChangeLog/library/src)
LOCAL_SRC_FILES += $(call all-java-files-under, plugins/HoloColorPicker/src)
res_dir := res plugins/ActionBarSherlock/library/res plugins/Android-PullToRefresh/library/res plugins/ckChangeLog/library/res plugins/HoloColorPicker/res
LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dir))
LOCAL_SDK_VERSION := current
LOCAL_PACKAGE_NAME := Email
LOCAL_AAPT_FLAGS := --auto-add-overlay
LOCAL_AAPT_FLAGS += --extra-packages de.cketti.library.changelog
LOCAL_AAPT_FLAGS += --extra-packages android.support.v4.app
LOCAL_AAPT_FLAGS += --extra-packages com.actionbarsherlock
LOCAL_AAPT_FLAGS += --extra-packages com.handmark.pulltorefresh.library
LOCAL_AAPT_FLAGS += --extra-packages com.larswerkman.colorpicker
LOCAL_PROGUARD_FLAG_FILES := proguard.cfg
include $(BUILD_PACKAGE)
##################################################
include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libcore:libs/apache-mime4j-core-0.7.2.jar
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libdom:libs/apache-mime4j-dom-0.7.2.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)

View File

83
README.md Normal file
View 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)

View File

@ -1,45 +1,22 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.7.+'
}
}
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 19
buildToolsVersion '18.1.1'
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
repositories {
jcenter()
}
instrumentTest {
manifest.srcFile 'tests/AndroidManifest.xml'
java.srcDirs = ['tests/src']
assets.srcDirs = ['tests/assets']
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
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
}
}
}
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
}
}

472
build.xml
View File

@ -1,472 +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>
<!-- Check that there is no existing git tag for the new version -->
<exec executable="git" failonerror="true" outputproperty="git-tag-status" errorproperty="git-tag-status-error">
<arg line="tag --list ${version-name}" />
</exec>
<if.contrib>
<equals arg1="${git-tag-status}" arg2="" />
<else>
<fail>A git tag for version ${version-name} already 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=&quot;${current-version-name}&quot;"
value="android:versionName=&quot;${version-name}&quot;" summary="true"
/>
<math result="new-version-code" operand1="${current-version-code}" operation="+" operand2="1" datatype="int"/>
<replace file="AndroidManifest.xml"
token="android:versionCode=&quot;${current-version-code}&quot;"
value="android:versionCode=&quot;${new-version-code}&quot;" 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>

View File

@ -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>

Binary file not shown.

View 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>

View File

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<FindBugsFilter>
<Match>
<Class name="~com\.fsck\.k9\.R.*" />
</Match>
<Match>
<Class name="~android\..*" />
</Match>
</FindBugsFilter>

View 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
View 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>

View File

View File

@ -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?

View File

@ -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.

View 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()
}

View 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)
}
}

View File

@ -1,6 +1,6 @@
#Mon Nov 04 16:58:06 CET 2013
#Sun Dec 07 14:12:42 GMT 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

1479
images/feature_graphic.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 84 KiB

19
images/update-drawables-pgp.sh Executable file
View 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

View 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'
}
}

View File

@ -5,23 +5,38 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.codec.Base64InputStream;
import org.apache.james.mime4j.util.MimeUtil;
import java.util.Date;
import java.util.TimeZone;
import android.test.AndroidTestCase;
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.MimeUtility;
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;
public class MessageTest extends AndroidTestCase {
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"
@ -191,7 +206,8 @@ public class MessageTest extends AndroidTestCase {
+ "Content-Transfer-Encoding: 7bit\r\n"
+ "\r\n"
+ "------Boundary102\r\n"
+ "Content-Type: text/plain; charset=utf-8\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"
@ -200,7 +216,8 @@ public class MessageTest extends AndroidTestCase {
+ "End of test=2E\r\n"
+ "\r\n"
+ "------Boundary102\r\n"
+ "Content-Type: text/plain; charset=utf-8\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"
@ -228,7 +245,8 @@ public class MessageTest extends AndroidTestCase {
+ "Content-Transfer-Encoding: 7bit\r\n"
+ "\r\n"
+ "------Boundary101\r\n"
+ "Content-Type: text/plain; charset=utf-8\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"
@ -237,7 +255,8 @@ public class MessageTest extends AndroidTestCase {
+ "End of test=2E\r\n"
+ "\r\n"
+ "------Boundary101\r\n"
+ "Content-Type: text/plain; charset=utf-8\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"
@ -259,15 +278,37 @@ public class MessageTest extends AndroidTestCase {
private int mMimeBoundary;
public MessageTest() {
super();
@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(getContext().getCacheDir());
BinaryTempFileBody.setTempDirectory(InstrumentationRegistry.getTargetContext().getCacheDir());
mMimeBoundary = 101;
message = nestedMessage(nestedMessage(sampleMessage()));
@ -285,8 +326,7 @@ public class MessageTest extends AndroidTestCase {
private MimeMessage nestedMessage(MimeMessage subMessage)
throws MessagingException, IOException {
BinaryTempFileMessageBody tempMessageBody = new BinaryTempFileMessageBody();
tempMessageBody.setEncoding(MimeUtil.ENC_8BIT);
BinaryTempFileMessageBody tempMessageBody = new BinaryTempFileMessageBody(MimeUtil.ENC_8BIT);
OutputStream out = tempMessageBody.getOutputStream();
try {
@ -318,7 +358,7 @@ public class MessageTest extends AndroidTestCase {
multipartBody.addBodyPart(textBodyPart(MimeUtil.ENC_8BIT));
multipartBody.addBodyPart(textBodyPart(MimeUtil.ENC_QUOTED_PRINTABLE));
multipartBody.addBodyPart(binaryBodyPart());
message.setBody(multipartBody);
MimeMessageHelper.setBody(message, multipartBody);
return message;
}
@ -326,12 +366,12 @@ public class MessageTest extends AndroidTestCase {
private MimeBodyPart binaryBodyPart() throws IOException,
MessagingException {
String encodedTestString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz0123456789+/";
+ "abcdefghijklmnopqrstuvwxyz0123456789+/\r\n";
BinaryTempFileBody tempFileBody = new BinaryTempFileBody();
BinaryTempFileBody tempFileBody = new BinaryTempFileBody(MimeUtil.ENC_BASE64);
InputStream in = new Base64InputStream(new ByteArrayInputStream(
encodedTestString.getBytes("UTF-8")));
InputStream in = new ByteArrayInputStream(
encodedTestString.getBytes("UTF-8"));
OutputStream out = tempFileBody.getOutputStream();
try {
@ -356,7 +396,7 @@ public class MessageTest extends AndroidTestCase {
+ "End of test.\r\n");
textBody.setCharset("utf-8");
MimeBodyPart bodyPart = new MimeBodyPart(textBody, "text/plain");
MimeUtility.setCharset("utf-8", bodyPart);
CharsetSupport.setCharset("utf-8", bodyPart);
bodyPart.setEncoding(encoding);
return bodyPart;
}
@ -374,7 +414,5 @@ public class MessageTest extends AndroidTestCase {
sb.append(Integer.toString(mMimeBoundary++));
return sb.toString();
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -1,20 +1,29 @@
package com.fsck.k9.net.ssl;
import javax.net.ssl.X509TrustManager;
import com.fsck.k9.security.LocalKeyStore;
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.test.AndroidTestCase;
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}.
*/
public class TrustManagerFactoryTest extends AndroidTestCase {
@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;
@ -96,70 +105,82 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
+ "Dcf5/G8bZe2DnavBQfML1wI5d7NUWE8CWb95SsIvFXI0qZE0oIR+axBVl9u97uaO\n"
+ "-----END CERTIFICATE-----\n";
private static final String STARFIELD_CERT =
private static final String LINUX_COM_FIRST_PARENT_CERT =
"-----BEGIN CERTIFICATE-----\n" +
"MIIFBzCCA++gAwIBAgICAgEwDQYJKoZIhvcNAQEFBQAwaDELMAkGA1UEBhMCVVMx\n" +
"JTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsT\n" +
"KVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2\n" +
"MTExNjAxMTU0MFoXDTI2MTExNjAxMTU0MFowgdwxCzAJBgNVBAYTAlVTMRAwDgYD\n" +
"VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy\n" +
"ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTkwNwYDVQQLEzBodHRwOi8vY2VydGlm\n" +
"aWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkxMTAvBgNVBAMTKFN0\n" +
"YXJmaWVsZCBTZWN1cmUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxETAPBgNVBAUT\n" +
"CDEwNjg4NDM1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4qddo+1m\n" +
"72ovKzYf3Y3TBQKgyg9eGa44cs8W2lRKy0gK9KFzEWWFQ8lbFwyaK74PmFF6YCkN\n" +
"bN7i6OUVTVb/kNGnpgQ/YAdKym+lEOez+FyxvCsq3AF59R019Xoog/KTc4KJrGBt\n" +
"y8JIwh3UBkQXPKwBR6s+cIQJC7ggCEAgh6FjGso+g9I3s5iNMj83v6G3W1/eXDOS\n" +
"zz4HzrlIS+LwVVAv+HBCidGTlopj2WYN5lhuuW2QvcrchGbyOY5bplhVc8tibBvX\n" +
"IBY7LFn1y8hWMkpQJ7pV06gBy3KpdIsMrTrlFbYq32X43or174Q7+edUZQuAvUdF\n" +
"pfBE2FM7voDxLwIDAQABo4IBRDCCAUAwHQYDVR0OBBYEFElLUifRG7zyoSFqYntR\n" +
"QnqK19VWMB8GA1UdIwQYMBaAFL9ft9HO3R+G9FtVrNzXEMIOqYjnMBIGA1UdEwEB\n" +
"/wQIMAYBAf8CAQAwOQYIKwYBBQUHAQEELTArMCkGCCsGAQUFBzABhh1odHRwOi8v\n" +
"b2NzcC5zdGFyZmllbGR0ZWNoLmNvbTBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8v\n" +
"Y2VydGlmaWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkvc2Zyb290\n" +
"LmNybDBRBgNVHSAESjBIMEYGBFUdIAAwPjA8BggrBgEFBQcCARYwaHR0cDovL2Nl\n" +
"cnRpZmljYXRlcy5zdGFyZmllbGR0ZWNoLmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB\n" +
"/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAhlK6sx+mXmuQpmQq/EWyrp8+s2Kv\n" +
"2x9nxL3KoS/HnA0hV9D4NiHOOiU+eHaz2d283vtshF8Mow0S6xE7cV+AHvEfbQ5f\n" +
"wezUpfdlux9MlQETsmqcC+sfnbHn7RkNvIV88xe9WWOupxoFzUfjLZZiUTIKCGhL\n" +
"Indf90XcYd70yysiKUQl0p8Ld3qhJnxK1w/C0Ty6DqeVmlsFChD5VV/Bl4t0zF4o\n" +
"aRN+0AqNnQ9gVHrEjBs1D3R6cLKCzx214orbKsayUWm/EheSYBeqPVsJ+IdlHaek\n" +
"KOUiAgOCRJo0Y577KM/ozS4OUiDtSss4fJ2ubnnXlSyokfOGASGRS7VApA==\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" +
"MIIFfDCCBGSgAwIBAgIHJ7DOOMo+MDANBgkqhkiG9w0BAQUFADCB3DELMAkGA1UE\n" +
"BhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAj\n" +
"BgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOTA3BgNVBAsTMGh0\n" +
"dHA6Ly9jZXJ0aWZpY2F0ZXMuc3RhcmZpZWxkdGVjaC5jb20vcmVwb3NpdG9yeTEx\n" +
"MC8GA1UEAxMoU3RhcmZpZWxkIFNlY3VyZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0\n" +
"eTERMA8GA1UEBRMIMTA2ODg0MzUwHhcNMTExMDA1MDI1MTQyWhcNMTQxMDA1MDI1\n" +
"MTQyWjBPMRQwEgYDVQQKFAsqLmxpbnV4LmNvbTEhMB8GA1UECxMYRG9tYWluIENv\n" +
"bnRyb2wgVmFsaWRhdGVkMRQwEgYDVQQDFAsqLmxpbnV4LmNvbTCCASIwDQYJKoZI\n" +
"hvcNAQEBBQADggEPADCCAQoCggEBANoZR/TDp2/8LtA8k9Li55I665ssC7rHX+Wk\n" +
"oiGa6xBeCKTvNy9mgaUVzHwrOQlwJ2GbxFI+X0e3W2sWXUDTSxESZSEW2VZnjEn2\n" +
"600Qm8XMhZPvqztLRweHH8IuBNNYZHnW4Z2L4DS/Mi03EmjKZt2g3heGQbrv74m4\n" +
"v9/g6Jgr5ZOIwES6LUJchSWV2zcL8VYunpxnAtbi2hq1YfA9oYU82ngP40Ds7HEB\n" +
"9pUlzcWu9gcasWGzTvbVBZ4nA29pz5zWn1LHYfSYVSmXKU/ggfZb2nXd5/NkbWQX\n" +
"7B2SNH9/OVrHtFZldzD1+ddfCt1DQjXfGv7QqpAVsFTdKspPDLMCAwEAAaOCAc0w\n" +
"ggHJMA8GA1UdEwEB/wQFMAMBAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF\n" +
"BwMCMA4GA1UdDwEB/wQEAwIFoDA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vY3Js\n" +
"LnN0YXJmaWVsZHRlY2guY29tL3NmczEtMjAuY3JsMFkGA1UdIARSMFAwTgYLYIZI\n" +
"AYb9bgEHFwEwPzA9BggrBgEFBQcCARYxaHR0cDovL2NlcnRpZmljYXRlcy5zdGFy\n" +
"ZmllbGR0ZWNoLmNvbS9yZXBvc2l0b3J5LzCBjQYIKwYBBQUHAQEEgYAwfjAqBggr\n" +
"BgEFBQcwAYYeaHR0cDovL29jc3Auc3RhcmZpZWxkdGVjaC5jb20vMFAGCCsGAQUF\n" +
"BzAChkRodHRwOi8vY2VydGlmaWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9z\n" +
"aXRvcnkvc2ZfaW50ZXJtZWRpYXRlLmNydDAfBgNVHSMEGDAWgBRJS1In0Ru88qEh\n" +
"amJ7UUJ6itfVVjAhBgNVHREEGjAYggsqLmxpbnV4LmNvbYIJbGludXguY29tMB0G\n" +
"A1UdDgQWBBQ44sIiZfPIl4PY51fh2TCZkqtToTANBgkqhkiG9w0BAQUFAAOCAQEA\n" +
"HFMuDtEZ+hIrIp4hnRJXUiTsc4Vaycxd5X/axDzUx+ooT3y2jBw0rcNnFhgD1T3u\n" +
"9zKiOLGXidvy2G/ppy/ymE+gcNqcEzfV1pKggNqStCwpEX1K8GBD46mX5qJ1RxI+\n" +
"QoHo/FZe7Vt+dQjHHdGWh27iVWadpBo/FJnHOsTaHewKL8+Aho0M84nxnUolYxzC\n" +
"9H3ViEz+mfMISLzvWicxVU71aJ4yI9JmaL1ddRppBovZHOeWshizcMVtFwcza1S0\n" +
"ZfajonXj48ZkXMXGWuomWxE2dGro6ZW6DdyIjTpZHCJuIvGC10J3mHIR5XaTj6mv\n" +
"zkVBz5DhpshQe97x6OGLOA==\n" +
"-----END CERTIFICATE-----\n";
"-----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;
@ -167,7 +188,7 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
private X509Certificate mCert2;
private X509Certificate mCaCert;
private X509Certificate mCert3;
private X509Certificate mStarfieldCert;
private X509Certificate mLinuxComFirstParentCert;
private X509Certificate mLinuxComCert;
@ -176,7 +197,7 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
mCert2 = loadCert(K9_EXAMPLE_COM_CERT2);
mCaCert = loadCert(CA_CERT);
mCert3 = loadCert(CERT3);
mStarfieldCert = loadCert(STARFIELD_CERT);
mLinuxComFirstParentCert = loadCert(LINUX_COM_FIRST_PARENT_CERT);
mLinuxComCert = loadCert(LINUX_COM_CERT);
}
@ -186,15 +207,16 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
new ByteArrayInputStream(encodedCert.getBytes()));
}
@Override
@Before
public void setUp() throws Exception {
mKeyStoreFile = File.createTempFile("localKeyStore", null, getContext().getCacheDir());
mKeyStoreFile = File.createTempFile("localKeyStore", null,
InstrumentationRegistry.getTargetContext().getCacheDir());
mKeyStore = LocalKeyStore.getInstance();
mKeyStore.setKeyStoreFile(mKeyStoreFile);
}
@Override
protected void tearDown() {
@After
public void tearDown() {
mKeyStoreFile.delete();
}
@ -210,6 +232,7 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
* @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);
@ -220,24 +243,28 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
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);
@ -246,11 +273,13 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
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);
@ -258,6 +287,7 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
trustManager.checkServerTrusted(new X509Certificate[] { mCert3, mCaCert }, "authType");
}
@Test
public void testLocallyTrustedCertificateChainNotMatchingHost() throws Exception {
mKeyStore.addCertificate(NOT_MATCHING_HOST, PORT1, mCert3);
@ -265,22 +295,25 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
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, mStarfieldCert };
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, mStarfieldCert });
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, mStarfieldCert };
X509Certificate[] certificates = new X509Certificate[] { mLinuxComCert, mLinuxComFirstParentCert};
trustManager.checkServerTrusted(certificates, "authType");
}
@ -296,6 +329,7 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
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);

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.fsck.k9.mail" />

View File

@ -3,6 +3,7 @@ package com.fsck.k9.mail;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.codec.EncoderUtil;
@ -10,43 +11,24 @@ import org.apache.james.mime4j.dom.address.Mailbox;
import org.apache.james.mime4j.dom.address.MailboxList;
import org.apache.james.mime4j.field.address.AddressBuilder;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.text.TextUtils;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.helper.Utility;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
public class Address {
/**
* 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;
private static final Pattern ATOM = Pattern.compile("^(?:[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]|\\s)+$");
/**
* Immutable empty {@link Address} array
*/
private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0];
String mAddress;
private String mAddress;
String mPersonal;
private String mPersonal;
public Address(Address address) {
@ -69,7 +51,7 @@ public class Address {
Rfc822Token token = tokens[0];
mAddress = token.getAddress();
String name = token.getName();
if (!StringUtils.isNullOrEmpty(name)) {
if (!TextUtils.isEmpty(name)) {
/*
* Don't use the "personal" argument if "address" is of the form:
* James Bond <james.bond@mi6.uk>
@ -93,6 +75,16 @@ public class Address {
return mAddress;
}
public String getHostname() {
int hostIdx = mAddress.lastIndexOf("@");
if (hostIdx == -1) {
return null;
}
return mAddress.substring(hostIdx+1);
}
public void setAddress(String address) {
this.mAddress = address;
}
@ -120,11 +112,11 @@ public class Address {
*/
public static Address[] parseUnencoded(String addressList) {
List<Address> addresses = new ArrayList<Address>();
if (!StringUtils.isNullOrEmpty(addressList)) {
if (!TextUtils.isEmpty(addressList)) {
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
for (Rfc822Token token : tokens) {
String address = token.getAddress();
if (!StringUtils.isNullOrEmpty(address)) {
if (!TextUtils.isEmpty(address)) {
addresses.add(new Address(token.getAddress(), token.getName(), false));
}
}
@ -140,7 +132,7 @@ public class Address {
* @return An array of 0 or more Addresses.
*/
public static Address[] parse(String addressList) {
if (StringUtils.isNullOrEmpty(addressList)) {
if (TextUtils.isEmpty(addressList)) {
return EMPTY_ADDRESS_ARRAY;
}
List<Address> addresses = new ArrayList<Address>();
@ -153,12 +145,12 @@ public class Address {
Mailbox mailbox = (Mailbox)address;
addresses.add(new Address(mailbox.getLocalPart() + "@" + mailbox.getDomain(), mailbox.getName(), false));
} else {
Log.e(K9.LOG_TAG, "Unknown address type from Mime4J: "
Log.e(LOG_TAG, "Unknown address type from Mime4J: "
+ address.getClass().toString());
}
}
} 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
addresses.add(new Address(null, addressList, false));
}
@ -188,8 +180,8 @@ public class Address {
@Override
public String toString() {
if (!StringUtils.isNullOrEmpty(mPersonal)) {
return Utility.quoteAtoms(mPersonal) + " <" + mAddress + ">";
if (!TextUtils.isEmpty(mPersonal)) {
return quoteAtoms(mPersonal) + " <" + mAddress + ">";
} else {
return mAddress;
}
@ -203,7 +195,7 @@ public class Address {
}
public String toEncodedString() {
if (!StringUtils.isNullOrEmpty(mPersonal)) {
if (!TextUtils.isEmpty(mPersonal)) {
return EncoderUtil.encodeAddressDisplayName(mPersonal) + " <" + mAddress + ">";
} else {
return mAddress;
@ -224,77 +216,6 @@ public class Address {
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()
@ -305,7 +226,7 @@ public class Address {
if (addressList == null) {
return new Address[] { };
}
ArrayList<Address> addresses = new ArrayList<Address>();
List<Address> addresses = new ArrayList<Address>();
int length = addressList.length();
int pairStartIndex = 0;
int pairEndIndex = 0;
@ -359,4 +280,44 @@ public class Address {
}
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;
}
}
}

View 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
}

View 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;
}

View 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;
}

View File

@ -13,14 +13,8 @@ public class CertificateChainException extends CertificateException {
private static final long serialVersionUID = 1103894512106650107L;
private X509Certificate[] mCertChain;
public CertificateChainException(String msg, X509Certificate[] chain) {
super(msg);
setCertChain(chain);
}
public CertificateChainException(CertificateException ce,
X509Certificate[] chain) {
super.initCause(ce);
public CertificateChainException(String msg, X509Certificate[] chain, Throwable cause) {
super(msg, cause);
setCertChain(chain);
}
@ -30,5 +24,4 @@ public class CertificateChainException extends CertificateException {
public X509Certificate[] getCertChain() {
return mCertChain;
}
}

View File

@ -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;
}
}

View File

@ -23,7 +23,6 @@ public interface CompositeBody extends Body {
* @throws MessagingException
*
*/
public abstract void setUsing7bitTransport() throws MessagingException;
}

View File

@ -0,0 +1,7 @@
package com.fsck.k9.mail;
public enum ConnectionSecurity {
NONE,
STARTTLS_REQUIRED,
SSL_TLS_REQUIRED
}

View File

@ -3,17 +3,13 @@ package com.fsck.k9.mail;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
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 {
protected final Account mAccount;
public abstract class Folder<T extends Message> {
private String status = null;
private long lastChecked = 0;
private long lastPush = 0;
@ -30,10 +26,6 @@ public abstract class Folder {
HOLDS_FOLDERS, HOLDS_MESSAGES,
}
protected Folder(Account account) {
mAccount = account;
}
/**
* Forces an open of the MailProvider. If the provider is already open this
* function returns without doing anything.
@ -81,7 +73,7 @@ public abstract class Folder {
public abstract int getUnreadMessageCount() 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.
@ -92,7 +84,7 @@ public abstract class Folder {
* @return List of messages
* @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
@ -102,36 +94,36 @@ public abstract class Folder {
* @param listener Listener to notify as we download 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);
}
public abstract Message[] getMessages(String[] uids, MessageRetrievalListener listener)
public abstract List<T> getMessages(String[] uids, MessageRetrievalListener<T> listener)
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;
}
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;
}
public void delete(Message[] msgs, String trashFolderName) throws MessagingException {
public void delete(List<? extends Message> msgs, String trashFolderName) throws MessagingException {
for (Message message : msgs) {
Message myMessage = getMessage(message.getUid());
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;
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;
@ -146,16 +138,16 @@ public abstract class Folder {
* @param listener Listener to notify as we fetch messages.
* @throws MessagingException
*/
public abstract void fetch(Message[] messages, FetchProfile fp,
MessageRetrievalListener listener) throws MessagingException;
public abstract void fetch(List<T> messages, FetchProfile fp,
MessageRetrievalListener<T> listener) throws MessagingException;
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
//throw new RuntimeException("fetchPart() not implemented.");
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "fetchPart() not implemented.");
if (K9MailLib.isDebug())
Log.d(LOG_TAG, "fetchPart() not implemented.");
}
public abstract void delete(boolean recurse) throws MessagingException;
@ -170,7 +162,6 @@ public abstract class Folder {
protected boolean mCanCreateKeywords = false;
/**
*
* @param oldPushState
* @param message
* @return empty string to clear the pushState, null to leave the state as-is
@ -223,10 +214,6 @@ public abstract class Folder {
return getSyncClass();
}
public void refresh(Preferences preferences) throws MessagingException {
}
public boolean isInTopGroup() {
return false;
}
@ -239,11 +226,7 @@ public abstract class Folder {
this.status = status;
}
public Account getAccount() {
return mAccount;
}
public List<Message> search(String queryString, final Flag[] requiredFlags, final Flag[] forbiddenFlags)
public List<T> search(String queryString, final Set<Flag> requiredFlags, final Set<Flag> forbiddenFlags)
throws MessagingException {
throw new MessagingException("K-9 does not support searches on this folder type");
}

View 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;
}
}
}

View File

@ -2,23 +2,19 @@
package com.fsck.k9.mail;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.EnumSet;
import java.util.Set;
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.EOLConvertingOutputStream;
import com.fsck.k9.mail.store.UnavailableStorageException;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
public abstract class Message implements Part, CompositeBody {
private static final Flag[] EMPTY_FLAG_ARRAY = new Flag[0];
private MessageReference mReference = null;
public enum RecipientType {
TO, CC, BCC,
@ -26,9 +22,9 @@ public abstract class Message implements Part, CompositeBody {
protected String mUid;
protected Set<Flag> mFlags = new HashSet<Flag>();
private Set<Flag> mFlags = EnumSet.noneOf(Flag.class);
protected Date mInternalDate;
private Date mInternalDate;
protected Folder mFolder;
@ -45,15 +41,15 @@ public abstract class Message implements Part, CompositeBody {
}
return false;
}
@Override
public boolean equals(Object o) {
if (o == null || !(o instanceof Message)) {
return false;
}
Message other = (Message)o;
return (mUid.equals(other.getUid())
&& mFolder.getName().equals(other.getFolder().getName())
&& mFolder.getAccount().getUuid().equals(other.getFolder().getAccount().getUuid()));
return (getUid().equals(other.getUid())
&& getFolder().getName().equals(other.getFolder().getName()));
}
@Override
@ -62,7 +58,6 @@ public abstract class Message implements Part, CompositeBody {
int result = 1;
result = MULTIPLIER * result + mFolder.getName().hashCode();
result = MULTIPLIER * result + mFolder.getAccount().getUuid().hashCode();
result = MULTIPLIER * result + mUid.hashCode();
return result;
}
@ -72,7 +67,6 @@ public abstract class Message implements Part, CompositeBody {
}
public void setUid(String uid) {
mReference = null;
this.mUid = uid;
}
@ -94,7 +88,7 @@ public abstract class Message implements Part, CompositeBody {
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;
@ -123,83 +117,43 @@ public abstract class Message implements Part, CompositeBody {
public abstract void setReferences(String references) throws MessagingException;
@Override
public abstract Body getBody();
public abstract String getContentType() throws MessagingException;
@Override
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;
@Override
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 setBody(Body body) throws MessagingException;
@Override
public abstract void setBody(Body body);
public abstract long getId();
public abstract String getPreview();
public abstract boolean hasAttachments();
/*
* 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 abstract int getSize();
public void delete(String trashFolderName) throws MessagingException {}
/*
* TODO Refactor Flags at some point to be able to store user defined flags.
*/
public Flag[] getFlags() {
return mFlags.toArray(EMPTY_FLAG_ARRAY);
public Set<Flag> getFlags() {
return Collections.unmodifiableSet(mFlags);
}
/**
@ -223,7 +177,7 @@ public abstract class Message implements Part, CompositeBody {
* @param flags
* @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) {
setFlag(flag, set);
}
@ -236,20 +190,11 @@ public abstract class Message implements Part, CompositeBody {
public void destroy() throws MessagingException {}
public abstract void setEncoding(String encoding) throws UnavailableStorageException, MessagingException;
@Override
public abstract void setEncoding(String encoding) 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() {
try {
@ -259,9 +204,9 @@ public abstract class Message implements Part, CompositeBody {
eolOut.flush();
return out.getCount();
} 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) {
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;
}
@ -269,17 +214,15 @@ public abstract class Message implements Part, CompositeBody {
/**
* Copy the contents of this object into another {@code Message} object.
*
* @param destination
* The {@code Message} object to receive the contents of this instance.
* @param destination The {@code Message} object to receive the contents of this instance.
*/
protected void copy(Message destination) {
destination.mUid = mUid;
destination.mInternalDate = mInternalDate;
destination.mFolder = mFolder;
destination.mReference = mReference;
// mFlags contents can change during the object lifetime, so copy the Set
destination.mFlags = new HashSet<Flag>(mFlags);
destination.mFlags = EnumSet.copyOf(mFlags);
}
/**
@ -293,6 +236,7 @@ public abstract class Message implements Part, CompositeBody {
* for more information.
* </p>
*/
@Override
public abstract Message clone();
public abstract void setUsing7bitTransport() throws MessagingException;
}

View File

@ -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 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>

View File

@ -4,7 +4,7 @@ package com.fsck.k9.mail;
public class MessagingException extends Exception {
public static final long serialVersionUID = -1;
boolean permanentFailure = false;
private boolean permanentFailure = false;
public MessagingException(String message) {
super(message);
@ -28,9 +28,9 @@ public class MessagingException extends Exception {
return permanentFailure;
}
//TODO setters in Exception are bad style, remove (it's nearly unused anyway)
public void setPermanentFailure(boolean permanentFailure) {
this.permanentFailure = permanentFailure;
}
}

View File

@ -2,51 +2,40 @@
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.MimeUtility;
import com.fsck.k9.mail.internet.CharsetSupport;
import com.fsck.k9.mail.internet.TextBody;
public abstract class Multipart implements CompositeBody {
protected Part mParent;
private Part mParent;
protected ArrayList<BodyPart> mParts = new ArrayList<BodyPart>();
protected String mContentType;
private final List<BodyPart> mParts = new ArrayList<BodyPart>();
public void addBodyPart(BodyPart part) {
mParts.add(part);
part.setParent(this);
}
public void addBodyPart(BodyPart part, int index) {
mParts.add(index, part);
part.setParent(this);
}
public BodyPart getBodyPart(int index) {
return mParts.get(index);
}
public String getContentType() {
return mContentType;
public List<BodyPart> getBodyParts() {
return Collections.unmodifiableList(mParts);
}
public abstract String getMimeType();
public abstract String getBoundary();
public int getCount() {
return mParts.size();
}
public boolean removeBodyPart(BodyPart part) {
part.setParent(null);
return mParts.remove(part);
}
public void removeBodyPart(int index) {
mParts.get(index).setParent(null);
mParts.remove(index);
}
public Part getParent() {
return mParent;
}
@ -55,6 +44,7 @@ public abstract class Multipart implements CompositeBody {
this.mParent = parent;
}
@Override
public void setEncoding(String encoding) throws MessagingException {
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
&& !MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
@ -72,8 +62,11 @@ public abstract class Multipart implements CompositeBody {
BodyPart part = mParts.get(0);
Body body = part.getBody();
if (body instanceof TextBody) {
MimeUtility.setCharset(charset, part);
CharsetSupport.setCharset(charset, part);
((TextBody)body).setCharset(charset);
}
}
public abstract byte[] getPreamble();
public abstract byte[] getEpilogue();
}

View File

@ -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;
}
}
}

View 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);
}

View File

@ -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);
}

View File

@ -3,7 +3,6 @@ package com.fsck.k9.mail;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.fsck.k9.Account;
/**
* 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.
* </p>
*
* @see Account#getStoreUri()
* @see Account#getTransportUri()
* @see com.fsck.k9.mail.store.StoreConfig#getStoreUri()
* @see com.fsck.k9.mail.store.StoreConfig#getTransportUri()
*/
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.
@ -64,6 +89,14 @@ public class ServerSettings {
*/
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.
*
@ -89,10 +122,12 @@ public class ServerSettings {
* see {@link ServerSettings#username}
* @param 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, AuthType authenticationType, String username,
String password) {
String password, String clientCertificateAlias) {
this.type = type;
this.host = host;
this.port = port;
@ -100,6 +135,7 @@ public class ServerSettings {
this.authenticationType = authenticationType;
this.username = username;
this.password = password;
this.clientCertificateAlias = clientCertificateAlias;
this.extra = null;
}
@ -120,12 +156,14 @@ public class ServerSettings {
* see {@link ServerSettings#username}
* @param password
* see {@link ServerSettings#password}
* @param clientCertificateAlias
* see {@link ServerSettings#clientCertificateAlias}
* @param extra
* see {@link ServerSettings#extra}
*/
public ServerSettings(String type, String host, int port,
public ServerSettings(Type type, String host, int port,
ConnectionSecurity connectionSecurity, AuthType authenticationType, String username,
String password, Map<String, String> extra) {
String password, String clientCertificateAlias, Map<String, String> extra) {
this.type = type;
this.host = host;
this.port = port;
@ -133,6 +171,7 @@ public class ServerSettings {
this.authenticationType = authenticationType;
this.username = username;
this.password = password;
this.clientCertificateAlias = clientCertificateAlias;
this.extra = (extra != null) ?
Collections.unmodifiableMap(new HashMap<String, String>(extra)) : null;
}
@ -145,7 +184,7 @@ public class ServerSettings {
* @param type
* see {@link ServerSettings#type}
*/
public ServerSettings(String type) {
public ServerSettings(Type type) {
this.type = type;
host = null;
port = -1;
@ -153,6 +192,7 @@ public class ServerSettings {
authenticationType = null;
username = null;
password = null;
clientCertificateAlias = null;
extra = null;
}
@ -173,6 +213,11 @@ public class ServerSettings {
public ServerSettings newPassword(String newPassword) {
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);
}
}

View 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");
}
}
}

View File

@ -1,22 +1,30 @@
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.WebDavTransport;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
public abstract class Transport {
protected static final int SOCKET_CONNECT_TIMEOUT = 10000;
// RFC 1047
protected static final int SOCKET_READ_TIMEOUT = 300000;
public synchronized static Transport getInstance(Account account) throws MessagingException {
String uri = account.getTransportUri();
public synchronized static Transport getInstance(Context context, StoreConfig storeConfig) throws MessagingException {
String uri = storeConfig.getTransportUri();
if (uri.startsWith("smtp")) {
return new SmtpTransport(account);
return new SmtpTransport(storeConfig, new DefaultTrustedSocketFactory(context));
} else if (uri.startsWith("webdav")) {
return new WebDavTransport(account);
return new WebDavTransport(storeConfig);
} else {
throw new MessagingException("Unable to locate an applicable Transport for " + uri);
}
@ -56,9 +64,9 @@ public abstract class Transport {
* @see WebDavTransport#createUri(ServerSettings)
*/
public static String createTransportUri(ServerSettings server) {
if (SmtpTransport.TRANSPORT_TYPE.equals(server.type)) {
if (Type.SMTP == server.type) {
return SmtpTransport.createUri(server);
} else if (WebDavTransport.TRANSPORT_TYPE.equals(server.type)) {
} else if (Type.WebDAV == server.type) {
return WebDavTransport.createUri(server);
} else {
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 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");
}
}
}

View File

@ -17,8 +17,8 @@
package com.fsck.k9.mail.filter;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.charset.Charset;
/**
* Provides Base64 encoding and decoding as defined by RFC 2045.
@ -34,6 +34,22 @@ import java.math.BigInteger;
* @version $Id$
*/
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.
*
@ -225,12 +241,8 @@ public class Base64 {
}
this.decodeSize = encodeSize - 1;
if (containsBase64Byte(lineSeparator)) {
String sep;
try {
sep = new String(lineSeparator, "UTF-8");
} catch (UnsupportedEncodingException uee) {
sep = new String(lineSeparator);
}
String sep = new String(lineSeparator, Charset.forName("UTF-8"));
throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]");
}
}

View File

@ -5,8 +5,11 @@ import java.io.IOException;
import java.io.OutputStream;
public class EOLConvertingOutputStream extends FilterOutputStream {
private static final int CR = '\r';
private static final int LF = '\n';
private int lastChar;
private boolean ignoreNextIfLF = false;
private static final int IGNORE_LF = Integer.MIN_VALUE;
public EOLConvertingOutputStream(OutputStream out) {
super(out);
@ -14,26 +17,27 @@ public class EOLConvertingOutputStream extends FilterOutputStream {
@Override
public void write(int oneByte) throws IOException {
if (!ignoreNextIfLF) {
if ((oneByte == '\n') && (lastChar != '\r')) {
super.write('\r');
}
super.write(oneByte);
lastChar = oneByte;
if (oneByte == LF && lastChar == IGNORE_LF) {
lastChar = LF;
return;
}
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
public void flush() throws IOException {
if (lastChar == '\r') {
super.write('\n');
lastChar = '\n';
if (lastChar == CR) {
super.write(LF);
// We have to ignore the next character if it is <LF>. Otherwise it
// will be expanded to an additional <CR><LF> sequence although it
// belongs to the one just completed.
ignoreNextIfLF = true;
lastChar = IGNORE_LF;
}
super.flush();
}

View File

@ -1,6 +1,5 @@
package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.filter.Base64OutputStream;
import org.apache.commons.io.IOUtils;
@ -15,7 +14,7 @@ import java.io.*;
* 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 Body {
public class BinaryTempFileBody implements RawDataBody, SizeAware {
private static File mTempDirectory;
private File mFile;
@ -26,15 +25,62 @@ public class BinaryTempFileBody implements Body {
mTempDirectory = tempDirectory;
}
public void setEncoding(String encoding) throws MessagingException {
mEncoding = encoding;
public static File getTempDirectory() {
return mTempDirectory;
}
public BinaryTempFileBody() {
if (mTempDirectory == null) {
throw new
RuntimeException("setTempDirectory has not been called on BinaryTempFileBody!");
@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 {
@ -54,27 +100,21 @@ public class BinaryTempFileBody implements Body {
public void writeTo(OutputStream out) throws IOException, MessagingException {
InputStream in = getInputStream();
try {
boolean closeStream = false;
if (MimeUtil.isBase64Encoding(mEncoding)) {
out = new Base64OutputStream(out);
closeStream = true;
} else if (MimeUtil.isQuotedPrintableEncoded(mEncoding)){
out = new QuotedPrintableOutputStream(out, false);
closeStream = true;
}
try {
IOUtils.copy(in, out);
} finally {
if (closeStream) {
out.close();
}
}
IOUtils.copy(in, out);
} finally {
in.close();
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);

View File

@ -11,13 +11,14 @@ import com.fsck.k9.mail.CompositeBody;
import com.fsck.k9.mail.MessagingException;
/**
* A {@link BinaryTempFileBody} extension containing a body of type
* message/rfc822. This relates to a BinaryTempFileBody the same way that a
* {@link LocalAttachmentMessageBody} relates to a {@link LocalAttachmentBody}.
*
* 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)
@ -45,7 +46,7 @@ public class BinaryTempFileMessageBody extends BinaryTempFileBody implements Com
IOUtils.copy(in, out);
}
} finally {
in.close();
IOUtils.closeQuietly(in);
}
}
@ -59,4 +60,4 @@ public class BinaryTempFileMessageBody extends BinaryTempFileBody implements Com
*/
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,16 +2,18 @@
package com.fsck.k9.mail.internet;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import java.io.ByteArrayInputStream;
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.QuotedPrintableInputStream;
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.
@ -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
* 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
* RFC 2047) found in a header field body.
@ -30,16 +32,11 @@ public class DecoderUtil {
* @return the decoded string.
*/
private static String decodeB(String encodedWord, String charset) {
byte[] bytes;
try {
bytes = encodedWord.getBytes("US-ASCII");
} catch (UnsupportedEncodingException e) {
return null;
}
byte[] bytes = encodedWord.getBytes(Charset.forName("US-ASCII"));
Base64InputStream is = new Base64InputStream(new ByteArrayInputStream(bytes));
try {
return MimeUtility.readToString(is, charset);
return CharsetSupport.readToString(is, charset);
} catch (IOException e) {
return null;
}
@ -68,16 +65,11 @@ public class DecoderUtil {
}
}
byte[] bytes;
try {
bytes = sb.toString().getBytes("US-ASCII");
} catch (UnsupportedEncodingException e) {
return null;
}
byte[] bytes = sb.toString().getBytes(Charset.forName("US-ASCII"));
QuotedPrintableInputStream is = new QuotedPrintableInputStream(new ByteArrayInputStream(bytes));
try {
return MimeUtility.readToString(is, charset);
return CharsetSupport.readToString(is, charset);
} catch (IOException e) {
return null;
}
@ -171,13 +163,13 @@ public class DecoderUtil {
String charset;
try {
charset = MimeUtility.fixupCharset(mimeCharset, message);
charset = CharsetSupport.fixupCharset(mimeCharset, message);
} catch (MessagingException e) {
return null;
}
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;
}
@ -186,7 +178,7 @@ public class DecoderUtil {
} else if (encoding.equalsIgnoreCase("B")) {
return DecoderUtil.decodeB(encodedText, charset);
} 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;
}
}

View File

@ -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
* 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 String ENC_WORD_PREFIX = "=?";
@ -68,7 +68,7 @@ public class EncoderUtil {
if (charset == null)
charset = determineCharset(text);
String mimeCharset = MimeUtility.getExternalCharset(charset.name());
String mimeCharset = CharsetSupport.getExternalCharset(charset.name());
byte[] bytes = encode(text, charset);

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -5,7 +5,6 @@ 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 com.fsck.k9.mail.Multipart;
import java.io.BufferedWriter;
import java.io.IOException;
@ -20,9 +19,8 @@ import org.apache.james.mime4j.util.MimeUtil;
* Message.
*/
public class MimeBodyPart extends BodyPart {
protected MimeHeader mHeader = new MimeHeader();
protected Body mBody;
protected int mSize;
private final MimeHeader mHeader = new MimeHeader();
private Body mBody;
public MimeBodyPart() throws MessagingException {
this(null);
@ -36,54 +34,46 @@ public class MimeBodyPart extends BodyPart {
if (mimeType != null) {
addHeader(MimeHeader.HEADER_CONTENT_TYPE, mimeType);
}
setBody(body);
MimeMessageHelper.setBody(this, body);
}
protected String getFirstHeader(String name) {
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;
}
public void setBody(Body body) throws MessagingException {
@Override
public void setBody(Body body) {
this.mBody = body;
if (body instanceof Multipart) {
Multipart multipart = ((Multipart)body);
multipart.setParent(this);
String type = multipart.getContentType();
setHeader(MimeHeader.HEADER_CONTENT_TYPE, type);
if ("multipart/signed".equalsIgnoreCase(type)) {
setEncoding(MimeUtil.ENC_7BIT);
} else {
setEncoding(MimeUtil.ENC_8BIT);
}
} else if (body instanceof TextBody) {
String contentType = String.format("%s;\r\n charset=utf-8", getMimeType());
String name = MimeUtility.getHeaderParameter(getContentType(), "name");
if (name != null) {
contentType += String.format(";\r\n name=\"%s\"", name);
}
setHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType);
setEncoding(MimeUtil.ENC_8BIT);
}
}
@Override
@ -94,16 +84,19 @@ public class MimeBodyPart extends BodyPart {
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
}
public String getContentType() throws MessagingException {
@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);
}
public String getContentId() throws MessagingException {
@Override
public String getContentId() {
String contentId = getFirstHeader(MimeHeader.HEADER_CONTENT_ID);
if (contentId == null) {
return null;
@ -117,21 +110,20 @@ public class MimeBodyPart extends BodyPart {
contentId;
}
public String getMimeType() throws MessagingException {
@Override
public String getMimeType() {
return MimeUtility.getHeaderParameter(getContentType(), null);
}
@Override
public boolean isMimeType(String mimeType) throws MessagingException {
return getMimeType().equalsIgnoreCase(mimeType);
}
public int getSize() {
return mSize;
}
/**
* 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);
@ -142,6 +134,11 @@ public class MimeBodyPart extends BodyPart {
}
}
@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);

View File

@ -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;
}
}

View File

@ -2,17 +2,20 @@
package com.fsck.k9.mail.internet;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.dom.field.DateTimeField;
import org.apache.james.mime4j.field.DefaultFieldParser;
@ -32,14 +35,13 @@ 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 com.fsck.k9.mail.store.UnavailableStorageException;
/**
* An implementation of Message that stores all of it's metadata in RFC 822 and
* RFC 2045 style headers.
*/
public class MimeMessage extends Message {
protected MimeHeader mHeader = new MimeHeader();
private MimeHeader mHeader = new MimeHeader();
protected Address[] mFrom;
protected Address[] mTo;
protected Address[] mCc;
@ -47,33 +49,20 @@ public class MimeMessage extends Message {
protected Address[] mReplyTo;
protected String mMessageId;
protected String[] mReferences;
protected String[] mInReplyTo;
private String[] mReferences;
private String[] mInReplyTo;
protected Date mSentDate;
protected SimpleDateFormat mDateFormat;
private Date mSentDate;
private SimpleDateFormat mDateFormat;
protected Body mBody;
private Body mBody;
protected int mSize;
private String serverExtra;
public MimeMessage() {
}
/**
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
* Nested messages will not be recursively parsed.
*
* @param in
* @throws IOException
* @throws MessagingException
*
* @see #MimeMessage(InputStream in, boolean recurse)
*/
public MimeMessage(InputStream in) throws IOException, MessagingException {
parse(in);
}
/**
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
*
@ -86,11 +75,15 @@ public class MimeMessage extends Message {
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);
}
protected void parse(InputStream in, boolean recurse) throws IOException, MessagingException {
private void parse(InputStream in, boolean recurse) throws IOException, MessagingException {
mHeader.clear();
mFrom = null;
mTo = null;
@ -119,8 +112,8 @@ public class MimeMessage extends Message {
try {
parser.parse(new EOLConvertingInputStream(in));
} catch (MimeException me) {
//TODO wouldn't a MessagingException be better?
throw new Error(me);
}
}
@ -146,18 +139,23 @@ public class MimeMessage extends Message {
* @param sentDate
* @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) {
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));
setInternalSentDate(sentDate);
}
@Override
public void setSentDate(Date sentDate) throws MessagingException {
public void setSentDate(Date sentDate, boolean hideTimeZone) throws MessagingException {
removeHeader("Date");
addSentDate(sentDate);
addSentDate(sentDate, hideTimeZone);
}
public void setInternalSentDate(Date sentDate) {
@ -165,25 +163,32 @@ public class MimeMessage extends Message {
}
@Override
public String getContentType() throws MessagingException {
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);
}
public String getContentId() throws MessagingException {
@Override
public String getContentId() {
return null;
}
public String getMimeType() throws MessagingException {
@Override
public String getMimeType() {
return MimeUtility.getHeaderParameter(getContentType(), null);
}
@Override
public boolean isMimeType(String mimeType) throws MessagingException {
return getMimeType().equalsIgnoreCase(mimeType);
}
@Override
public int getSize() {
return mSize;
}
@ -306,17 +311,31 @@ public class MimeMessage extends Message {
if (mMessageId == null) {
mMessageId = getFirstHeader("Message-ID");
}
if (mMessageId == null) { // even after checking the header
setMessageId(generateMessageId());
}
return mMessageId;
}
private String generateMessageId() {
return "<" + UUID.randomUUID().toString() + "@email.android.com>";
public void generateMessageId() throws MessagingException {
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);
mMessageId = messageId;
}
@ -377,55 +396,45 @@ public class MimeMessage extends Message {
}
@Override
public void setBody(Body body) throws MessagingException {
public void setBody(Body body) {
this.mBody = body;
setHeader("MIME-Version", "1.0");
if (body instanceof Multipart) {
Multipart multipart = ((Multipart)body);
multipart.setParent(this);
String type = multipart.getContentType();
setHeader(MimeHeader.HEADER_CONTENT_TYPE, type);
if ("multipart/signed".equalsIgnoreCase(type)) {
setEncoding(MimeUtil.ENC_7BIT);
} else {
setEncoding(MimeUtil.ENC_8BIT);
}
} else if (body instanceof TextBody) {
setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\r\n charset=utf-8",
getMimeType()));
setEncoding(MimeUtil.ENC_8BIT);
}
}
protected String getFirstHeader(String name) {
private String getFirstHeader(String name) {
return mHeader.getFirstHeader(name);
}
@Override
public void addHeader(String name, String value) throws UnavailableStorageException {
public void addHeader(String name, String value) throws MessagingException {
mHeader.addHeader(name, value);
}
@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);
}
@Override
public String[] getHeader(String name) throws UnavailableStorageException {
public String[] getHeader(String name) throws MessagingException {
return mHeader.getHeader(name);
}
@Override
public void removeHeader(String name) throws UnavailableStorageException {
public void removeHeader(String name) throws MessagingException {
mHeader.removeHeader(name);
}
@Override
public Set<String> getHeaderNames() throws UnavailableStorageException {
public Set<String> getHeaderNames() throws MessagingException {
return mHeader.getHeaderNames();
}
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
@ -437,6 +446,12 @@ public class MimeMessage extends Message {
}
}
@Override
public void writeHeaderTo(OutputStream out) throws IOException, MessagingException {
mHeader.writeTo(out);
}
@Override
public InputStream getInputStream() throws MessagingException {
return null;
}
@ -455,12 +470,12 @@ public class MimeMessage extends Message {
if (mBody instanceof Multipart) {
((Multipart)mBody).setCharset(charset);
} else if (mBody instanceof TextBody) {
MimeUtility.setCharset(charset, this);
CharsetSupport.setCharset(charset, this);
((TextBody)mBody).setCharset(charset);
}
}
class MimeMessageBuilder implements ContentHandler {
private class MimeMessageBuilder implements ContentHandler {
private final LinkedList<Object> stack = new LinkedList<Object>();
public MimeMessageBuilder() {
@ -473,43 +488,46 @@ public class MimeMessage extends Message {
}
}
@Override
public void startMessage() {
if (stack.isEmpty()) {
stack.addFirst(MimeMessage.this);
} else {
expect(Part.class);
try {
MimeMessage m = new MimeMessage();
((Part)stack.peek()).setBody(m);
stack.addFirst(m);
} catch (MessagingException me) {
throw new Error(me);
}
Part part = (Part) stack.peek();
MimeMessage m = new MimeMessage();
part.setBody(m);
stack.addFirst(m);
}
}
@Override
public void endMessage() {
expect(MimeMessage.class);
stack.removeFirst();
}
@Override
public void startHeader() {
expect(Part.class);
}
@Override
public void endHeader() {
expect(Part.class);
}
@Override
public void startMultipart(BodyDescriptor bd) {
expect(Part.class);
Part e = (Part)stack.peek();
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);
stack.addFirst(multiPart);
} catch (MessagingException me) {
@ -517,21 +535,37 @@ public class MimeMessage extends Message {
}
}
@Override
public void body(BodyDescriptor bd, InputStream in) throws IOException {
expect(Part.class);
try {
Body body = MimeUtility.decodeBody(in,
bd.getTransferEncoding(), bd.getMimeType());
Body body = MimeUtility.createBody(in, bd.getTransferEncoding(), bd.getMimeType());
((Part)stack.peek()).setBody(body);
} catch (MessagingException me) {
throw new Error(me);
}
}
@Override
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() {
expect(MimeMultipart.class);
@ -544,32 +578,29 @@ public class MimeMessage extends Message {
}
}
@Override
public void endBodyPart() {
expect(BodyPart.class);
stack.removeFirst();
}
public void epilogue(InputStream is) throws IOException {
expect(MimeMultipart.class);
StringBuilder sb = new StringBuilder();
int b;
while ((b = is.read()) != -1) {
sb.append((char)b);
}
// ((Multipart) stack.peek()).setEpilogue(sb.toString());
}
@Override
public void preamble(InputStream is) throws IOException {
expect(MimeMultipart.class);
StringBuilder sb = new StringBuilder();
int b;
while ((b = is.read()) != -1) {
sb.append((char)b);
}
((MimeMultipart)stack.peek()).setPreamble(sb.toString());
ByteArrayOutputStream preamble = new ByteArrayOutputStream();
IOUtils.copy(is, preamble);
((MimeMultipart)stack.peek()).setPreamble(preamble.toByteArray());
}
@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 {
throw new UnsupportedOperationException("Not supported");
}
@ -578,7 +609,9 @@ public class MimeMessage extends Message {
public void field(Field parsedField) throws MimeException {
expect(Part.class);
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) {
throw new Error(me);
}
@ -588,28 +621,27 @@ public class MimeMessage extends Message {
/**
* Copy the contents of this object into another {@code MimeMessage} object.
*
* @param message
* The {@code MimeMessage} object to receive the contents of this instance.
* @param destination The {@code MimeMessage} object to receive the contents of this instance.
*/
protected void copy(MimeMessage message) {
super.copy(message);
protected void copy(MimeMessage destination) {
super.copy(destination);
message.mHeader = mHeader.clone();
destination.mHeader = mHeader.clone();
message.mBody = mBody;
message.mMessageId = mMessageId;
message.mSentDate = mSentDate;
message.mDateFormat = mDateFormat;
message.mSize = mSize;
destination.mBody = mBody;
destination.mMessageId = mMessageId;
destination.mSentDate = mSentDate;
destination.mDateFormat = mDateFormat;
destination.mSize = mSize;
// These arrays are not supposed to be modified, so it's okay to reuse the references
message.mFrom = mFrom;
message.mTo = mTo;
message.mCc = mCc;
message.mBcc = mBcc;
message.mReplyTo = mReplyTo;
message.mReferences = mReferences;
message.mInReplyTo = mInReplyTo;
destination.mFrom = mFrom;
destination.mTo = mTo;
destination.mCc = mCc;
destination.mBcc = mBcc;
destination.mReplyTo = mReplyTo;
destination.mReferences = mReferences;
destination.mInReplyTo = mInReplyTo;
}
@Override
@ -619,14 +651,17 @@ public class MimeMessage extends Message {
return message;
}
@Override
public long getId() {
return Long.parseLong(mUid); //or maybe .mMessageId?
}
@Override
public String getPreview() {
return "";
}
@Override
public boolean hasAttachments() {
return false;
}
@ -673,4 +708,16 @@ public class MimeMessage extends Message {
setEncoding(MimeUtil.ENC_QUOTED_PRINTABLE);
}
}
@Override
public String getServerExtra() {
return serverExtra;
}
@Override
public void setServerExtra(String serverExtra) {
this.serverExtra = serverExtra;
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,119 @@
package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import java.io.*;
import java.util.Locale;
import java.util.Random;
public class MimeMultipart extends Multipart {
private String mimeType;
private byte[] preamble;
private byte[] epilogue;
private final String boundary;
public MimeMultipart() throws MessagingException {
boundary = generateBoundary();
setSubType("mixed");
}
public MimeMultipart(String mimeType, String boundary) throws MessagingException {
if (mimeType == null) {
throw new IllegalArgumentException("mimeType can't be null");
}
if (boundary == null) {
throw new IllegalArgumentException("boundary can't be null");
}
this.mimeType = mimeType;
this.boundary = boundary;
}
public String generateBoundary() {
Random random = new Random();
StringBuilder sb = new StringBuilder();
sb.append("----");
for (int i = 0; i < 30; i++) {
sb.append(Integer.toString(random.nextInt(36), 36));
}
return sb.toString().toUpperCase(Locale.US);
}
@Override
public String getBoundary() {
return boundary;
}
public byte[] getPreamble() {
return preamble;
}
public void setPreamble(byte[] preamble) {
this.preamble = preamble;
}
public byte[] getEpilogue() {
return epilogue;
}
public void setEpilogue(byte[] epilogue) {
this.epilogue = epilogue;
}
@Override
public String getMimeType() {
return mimeType;
}
public void setSubType(String subType) {
mimeType = "multipart/" + subType;
}
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
if (preamble != null) {
out.write(preamble);
writer.write("\r\n");
}
if (getBodyParts().isEmpty()) {
writer.write("--");
writer.write(boundary);
writer.write("\r\n");
} else {
for (BodyPart bodyPart : getBodyParts()) {
writer.write("--");
writer.write(boundary);
writer.write("\r\n");
writer.flush();
bodyPart.writeTo(out);
writer.write("\r\n");
}
}
writer.write("--");
writer.write(boundary);
writer.write("--\r\n");
writer.flush();
if (epilogue != null) {
out.write(epilogue);
}
}
@Override
public InputStream getInputStream() throws MessagingException {
return null;
}
@Override
public void setUsing7bitTransport() throws MessagingException {
for (BodyPart part : getBodyParts()) {
part.setUsing7bitTransport();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.Body;
/**
* See {@link MimeUtility#decodeBody(Body)}
*/
public interface RawDataBody extends Body {
String getEncoding();
}

View File

@ -0,0 +1,6 @@
package com.fsck.k9.mail.internet;
public interface SizeAware {
long getSize();
}

View File

@ -4,19 +4,24 @@ package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;
import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import com.fsck.k9.mail.filter.CountingOutputStream;
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
import org.apache.james.mime4j.util.MimeUtil;
public class TextBody implements Body {
public class TextBody implements Body, SizeAware {
/**
* Immutable empty byte array
*/
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private String mBody;
private final String mBody;
private String mEncoding;
private String mCharset = "UTF-8";
// Length of the message composed (as opposed to quoted). I don't like the name of this variable and am open to
@ -29,6 +34,7 @@ public class TextBody implements Body {
this.mBody = body;
}
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
if (mBody != null) {
byte[] bytes = mBody.getBytes(mCharset);
@ -54,6 +60,7 @@ public class TextBody implements Body {
/**
* Returns an InputStream that reads this body's text.
*/
@Override
public InputStream getInputStream() throws MessagingException {
try {
byte[] b;
@ -68,6 +75,7 @@ public class TextBody implements Body {
}
}
@Override
public void setEncoding(String encoding) {
mEncoding = encoding;
}
@ -91,4 +99,33 @@ public class TextBody implements Body {
public void setComposedMessageOffset(Integer composedMessageOffset) {
this.mComposedMessageOffset = composedMessageOffset;
}
@Override
public long getSize() {
try {
byte[] bytes = mBody.getBytes(mCharset);
if (MimeUtil.ENC_8BIT.equalsIgnoreCase(mEncoding)) {
return bytes.length;
} else {
return getLengthWhenQuotedPrintableEncoded(bytes);
}
} catch (IOException e) {
throw new RuntimeException("Couldn't get body size", e);
}
}
private long getLengthWhenQuotedPrintableEncoded(byte[] bytes) throws IOException {
CountingOutputStream countingOutputStream = new CountingOutputStream();
OutputStream quotedPrintableOutputStream = new QuotedPrintableOutputStream(countingOutputStream, false);
try {
quotedPrintableOutputStream.write(bytes);
} finally {
try {
quotedPrintableOutputStream.close();
} catch (IOException e) { /* ignore */ }
}
return countingOutputStream.getCount();
}
}

View File

@ -0,0 +1,104 @@
package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Part;
import java.util.List;
/**
* Empty marker class interface the class hierarchy used by
* {@link MessageExtractor#getViewables(com.fsck.k9.mail.Part, java.util.List)}
*
* @see Viewable.Text
* @see Viewable.Html
* @see Viewable.MessageHeader
* @see Viewable.Alternative
*/
public interface Viewable {
/**
* Class representing textual parts of a message that aren't marked as attachments.
*
* @see com.fsck.k9.mail.internet.MessageExtractor#isPartTextualBody(com.fsck.k9.mail.Part)
*/
abstract class Textual implements Viewable {
private Part mPart;
public Textual(Part part) {
mPart = part;
}
public Part getPart() {
return mPart;
}
}
/**
* Class representing a {@code text/plain} part of a message.
*/
class Text extends Textual {
public Text(Part part) {
super(part);
}
}
/**
* Class representing a {@code text/html} part of a message.
*/
class Html extends Textual {
public Html(Part part) {
super(part);
}
}
/**
* Class representing a {@code message/rfc822} part of a message.
*
* <p>
* This is used to extract basic header information when the message contents are displayed
* inline.
* </p>
*/
class MessageHeader implements Viewable {
private Part mContainerPart;
private Message mMessage;
public MessageHeader(Part containerPart, Message message) {
mContainerPart = containerPart;
mMessage = message;
}
public Part getContainerPart() {
return mContainerPart;
}
public Message getMessage() {
return mMessage;
}
}
/**
* Class representing a {@code multipart/alternative} part of a message.
*
* <p>
* Only relevant {@code text/plain} and {@code text/html} children are stored in this container
* class.
* </p>
*/
class Alternative implements Viewable {
private List<Viewable> mText;
private List<Viewable> mHtml;
public Alternative(List<Viewable> text, List<Viewable> html) {
mText = text;
mHtml = html;
}
public List<Viewable> getText() {
return mText;
}
public List<Viewable> getHtml() {
return mHtml;
}
}
}

View File

@ -0,0 +1,118 @@
package com.fsck.k9.mail.message;
import java.io.IOException;
import java.io.InputStream;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.parser.ContentHandler;
import org.apache.james.mime4j.parser.MimeStreamParser;
import org.apache.james.mime4j.stream.BodyDescriptor;
import org.apache.james.mime4j.stream.Field;
import org.apache.james.mime4j.stream.MimeConfig;
public class MessageHeaderParser {
public static void parse(final Part part, InputStream headerInputStream) throws MessagingException {
MimeStreamParser parser = getMimeStreamParser();
parser.setContentHandler(new MessageHeaderParserContentHandler(part));
try {
parser.parse(headerInputStream);
} catch (MimeException me) {
throw new MessagingException("Error parsing headers", me);
} catch (IOException e) {
throw new MessagingException("I/O error parsing headers", e);
}
}
private static MimeStreamParser getMimeStreamParser() {
MimeConfig parserConfig = new MimeConfig();
parserConfig.setMaxHeaderLen(-1);
parserConfig.setMaxLineLen(-1);
parserConfig.setMaxHeaderCount(-1);
return new MimeStreamParser(parserConfig);
}
private static class MessageHeaderParserContentHandler implements ContentHandler {
private final Part part;
public MessageHeaderParserContentHandler(Part part) {
this.part = part;
}
@Override
public void field(Field rawField) throws MimeException {
String name = rawField.getName();
String raw = rawField.getRaw().toString();
try {
part.addRawHeader(name, raw);
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
@Override
public void startMessage() throws MimeException {
/* do nothing */
}
@Override
public void endMessage() throws MimeException {
/* do nothing */
}
@Override
public void startBodyPart() throws MimeException {
/* do nothing */
}
@Override
public void endBodyPart() throws MimeException {
/* do nothing */
}
@Override
public void startHeader() throws MimeException {
/* do nothing */
}
@Override
public void endHeader() throws MimeException {
/* do nothing */
}
@Override
public void preamble(InputStream is) throws MimeException, IOException {
/* do nothing */
}
@Override
public void epilogue(InputStream is) throws MimeException, IOException {
/* do nothing */
}
@Override
public void startMultipart(BodyDescriptor bd) throws MimeException {
/* do nothing */
}
@Override
public void endMultipart() throws MimeException {
/* do nothing */
}
@Override
public void body(BodyDescriptor bd, InputStream is) throws MimeException, IOException {
/* do nothing */
}
@Override
public void raw(InputStream is) throws MimeException, IOException {
/* do nothing */
}
}
}

View File

@ -1,15 +1,19 @@
package com.fsck.k9.helper.power;
package com.fsck.k9.mail.power;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
import com.fsck.k9.K9;
import com.fsck.k9.mail.K9MailLib;
import android.content.Context;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
public class TracingPowerManager {
private final static boolean TRACE = false;
public static AtomicInteger wakeLockId = new AtomicInteger(0);
@ -20,8 +24,8 @@ public class TracingPowerManager {
public static synchronized TracingPowerManager getPowerManager(Context context) {
Context appContext = context.getApplicationContext();
if (tracingPowerManager == null) {
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "Creating TracingPowerManager");
if (K9MailLib.isDebug()) {
Log.v(LOG_TAG, "Creating TracingPowerManager");
}
tracingPowerManager = new TracingPowerManager(appContext);
}
@ -50,16 +54,16 @@ public class TracingPowerManager {
tag = ntag;
wakeLock = pm.newWakeLock(flags, tag);
id = wakeLockId.getAndIncrement();
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + ": Create");
if (K9MailLib.isDebug()) {
Log.v(LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + ": Create");
}
}
public void acquire(long timeout) {
synchronized (wakeLock) {
wakeLock.acquire(timeout);
}
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + " for " + timeout + " ms: acquired");
if (K9MailLib.isDebug()) {
Log.v(LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + " for " + timeout + " ms: acquired");
}
raiseNotification();
if (startTime == null) {
@ -72,8 +76,8 @@ public class TracingPowerManager {
wakeLock.acquire();
}
raiseNotification();
if (K9.DEBUG) {
Log.w(K9.LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + ": acquired with no timeout. K-9 Mail should not do this");
if (K9MailLib.isDebug()) {
Log.w(LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + ": acquired with no timeout. K-9 Mail should not do this");
}
if (startTime == null) {
startTime = System.currentTimeMillis();
@ -88,12 +92,12 @@ public class TracingPowerManager {
public void release() {
if (startTime != null) {
Long endTime = System.currentTimeMillis();
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + ": releasing after " + (endTime - startTime) + " ms, timeout = " + timeout + " ms");
if (K9MailLib.isDebug()) {
Log.v(LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + ": releasing after " + (endTime - startTime) + " ms, timeout = " + timeout + " ms");
}
} else {
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + ", timeout = " + timeout + " ms: releasing");
if (K9MailLib.isDebug()) {
Log.v(LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + ", timeout = " + timeout + " ms: releasing");
}
}
cancelNotification();
@ -123,11 +127,11 @@ public class TracingPowerManager {
public void run() {
if (startTime != null) {
Long endTime = System.currentTimeMillis();
Log.i(K9.LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + ": has been active for "
Log.i(LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + ": has been active for "
+ (endTime - startTime) + " ms, timeout = " + timeout + " ms");
} else {
Log.i(K9.LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + ": still active, timeout = " + timeout + " ms");
Log.i(LOG_TAG, "TracingWakeLock for tag " + tag + " / id " + id + ": still active, timeout = " + timeout + " ms");
}
}

View File

@ -1,29 +1,42 @@
package com.fsck.k9.net.ssl;
package com.fsck.k9.mail.ssl;
import android.util.Log;
import com.fsck.k9.K9;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import com.fsck.k9.mail.MessagingException;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
/**
* Filter and reorder list of cipher suites and TLS versions.
*/
public class TrustedSocketFactory {
protected static final String ENABLED_CIPHERS[];
protected static final String ENABLED_PROTOCOLS[];
public class DefaultTrustedSocketFactory implements TrustedSocketFactory {
protected static final String[] ENABLED_CIPHERS;
protected static final String[] ENABLED_PROTOCOLS;
// Order taken from OpenSSL 1.0.1c
protected static final String ORDERED_KNOWN_CIPHERS[] = {
protected static final String[] ORDERED_KNOWN_CIPHERS = {
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
@ -35,7 +48,6 @@ public class TrustedSocketFactory {
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
@ -43,14 +55,6 @@ public class TrustedSocketFactory {
"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDH_RSA_WITH_RC4_128_SHA",
"TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
"SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
"SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
"SSL_RSA_WITH_RC4_128_SHA",
"SSL_RSA_WITH_RC4_128_MD5",
};
protected static final String[] BLACKLISTED_CIPHERS = {
@ -61,33 +65,56 @@ public class TrustedSocketFactory {
"SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
"SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
"SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDH_RSA_WITH_RC4_128_SHA",
"TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
"SSL_RSA_WITH_RC4_128_SHA",
"SSL_RSA_WITH_RC4_128_MD5",
};
protected static final String ORDERED_KNOWN_PROTOCOLS[] = {
"TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3"
protected static final String[] ORDERED_KNOWN_PROTOCOLS = {
"TLSv1.2", "TLSv1.1", "TLSv1"
};
protected static final String[] BLACKLISTED_PROTOCOLS = {
"SSLv3"
};
static {
String[] enabledCiphers = null;
String[] enabledProtocols = null;
String[] supportedProtocols = null;
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, null, new SecureRandom());
sslContext.init(null, null, null);
SSLSocketFactory sf = sslContext.getSocketFactory();
SSLSocket sock = (SSLSocket) sf.createSocket();
enabledCiphers = sock.getEnabledCipherSuites();
enabledProtocols = sock.getEnabledProtocols();
/*
* Retrieve all supported protocols, not just the (default) enabled
* ones. TLSv1.1 & TLSv1.2 are supported on API levels 16+, but are
* only enabled by default on API levels 20+.
*/
supportedProtocols = sock.getSupportedProtocols();
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Error getting information about available SSL/TLS ciphers and " +
Log.e(LOG_TAG, "Error getting information about available SSL/TLS ciphers and " +
"protocols", e);
}
ENABLED_CIPHERS = (enabledCiphers == null) ? null :
reorder(enabledCiphers, ORDERED_KNOWN_CIPHERS, BLACKLISTED_CIPHERS);
ENABLED_PROTOCOLS = (enabledProtocols == null) ? null :
reorder(enabledProtocols, ORDERED_KNOWN_PROTOCOLS, null);
ENABLED_PROTOCOLS = (supportedProtocols == null) ? null :
reorder(supportedProtocols, ORDERED_KNOWN_PROTOCOLS, BLACKLISTED_PROTOCOLS);
}
public DefaultTrustedSocketFactory(Context context) {
this.context = context;
}
protected static String[] reorder(String[] enabled, String[] known, String[] blacklisted) {
@ -116,19 +143,29 @@ public class TrustedSocketFactory {
return result.toArray(new String[result.size()]);
}
public static Socket createSocket(SSLContext sslContext) throws IOException {
SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket();
hardenSocket(socket);
private Context context;
return socket;
}
public Socket createSocket(Socket socket, String host, int port, String clientCertificateAlias)
throws NoSuchAlgorithmException, KeyManagementException, MessagingException, IOException {
public static Socket createSocket(SSLContext sslContext, Socket s, String host, int port,
boolean autoClose) throws IOException {
SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket(s, host, port, autoClose);
hardenSocket(socket);
TrustManager[] trustManagers = new TrustManager[] { TrustManagerFactory.get(host, port) };
KeyManager[] keyManagers = null;
if (!TextUtils.isEmpty(clientCertificateAlias)) {
keyManagers = new KeyManager[] { new KeyChainKeyManager(context, clientCertificateAlias) };
}
return socket;
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
Socket trustedSocket;
if (socket == null) {
trustedSocket = socketFactory.createSocket();
} else {
trustedSocket = socketFactory.createSocket(socket, host, port, true);
}
hardenSocket((SSLSocket) trustedSocket);
setSNIHost(socketFactory, (SSLSocket) trustedSocket, host);
return trustedSocket;
}
private static void hardenSocket(SSLSocket sock) {
@ -139,4 +176,17 @@ public class TrustedSocketFactory {
sock.setEnabledProtocols(ENABLED_PROTOCOLS);
}
}
public static void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) {
if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
((android.net.SSLCertificateSocketFactory)factory).setHostname(socket, hostname);
} else {
try {
socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname);
} catch (Throwable e) {
// ignore any error, we just can't set the hostname...
Log.e(LOG_TAG, "Could not call SSLSocket#setHostname(String) method ", e);
}
}
}
}

View File

@ -0,0 +1,213 @@
package com.fsck.k9.mail.ssl;
import java.net.Socket;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.security.auth.x500.X500Principal;
import android.content.Context;
import android.os.Build;
import android.security.KeyChain;
import android.security.KeyChainException;
import android.util.Log;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.MessagingException;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
import static com.fsck.k9.mail.CertificateValidationException.Reason;
import static com.fsck.k9.mail.CertificateValidationException.Reason.RetrievalFailure;
/**
* For client certificate authentication! Provide private keys and certificates
* during the TLS handshake using the Android 4.0 KeyChain API.
*/
class KeyChainKeyManager extends X509ExtendedKeyManager {
private static PrivateKey sClientCertificateReferenceWorkaround;
private static synchronized void savePrivateKeyReference(PrivateKey privateKey) {
if (sClientCertificateReferenceWorkaround == null) {
sClientCertificateReferenceWorkaround = privateKey;
}
}
private final String mAlias;
private final X509Certificate[] mChain;
private final PrivateKey mPrivateKey;
/**
* @param alias Must not be null nor empty
* @throws MessagingException
* Indicates an error in retrieving the certificate for the alias
* (likely because the alias is invalid or the certificate was deleted)
*/
public KeyChainKeyManager(Context context, String alias) throws MessagingException {
mAlias = alias;
try {
mChain = fetchCertificateChain(context, alias);
mPrivateKey = fetchPrivateKey(context, alias);
} catch (KeyChainException e) {
// The certificate was possibly deleted. Notify user of error.
throw new CertificateValidationException(e.getMessage(), RetrievalFailure, alias);
} catch (InterruptedException e) {
throw new CertificateValidationException(e.getMessage(), RetrievalFailure, alias);
}
}
private X509Certificate[] fetchCertificateChain(Context context, String alias)
throws KeyChainException, InterruptedException, MessagingException {
X509Certificate[] chain = KeyChain.getCertificateChain(context, alias);
if (chain == null || chain.length == 0) {
throw new MessagingException("No certificate chain found for: " + alias);
}
try {
for (X509Certificate certificate : chain) {
certificate.checkValidity();
}
} catch (CertificateException e) {
throw new CertificateValidationException(e.getMessage(), Reason.Expired, alias);
}
return chain;
}
private PrivateKey fetchPrivateKey(Context context, String alias) throws KeyChainException,
InterruptedException, MessagingException {
PrivateKey privateKey = KeyChain.getPrivateKey(context, alias);
if (privateKey == null) {
throw new MessagingException("No private key found for: " + alias);
}
/*
* We need to keep reference to the first private key retrieved so
* it won't get garbage collected. If it will then the whole app
* will crash on Android < 4.2 with "Fatal signal 11 code=1". See
* https://code.google.com/p/android/issues/detail?id=62319
*/
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
savePrivateKeyReference(privateKey);
}
return privateKey;
}
@Override
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
return chooseAlias(keyTypes, issuers);
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return (mAlias.equals(alias) ? mChain : null);
}
@Override
public PrivateKey getPrivateKey(String alias) {
return (mAlias.equals(alias) ? mPrivateKey : null);
}
@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
return chooseAlias(new String[] { keyType }, issuers);
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
final String al = chooseAlias(new String[] { keyType }, issuers);
return (al == null ? null : new String[] { al });
}
@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
final String al = chooseAlias(new String[] { keyType }, issuers);
return (al == null ? null : new String[] { al });
}
@Override
public String chooseEngineClientAlias(String[] keyTypes, Principal[] issuers, SSLEngine engine) {
return chooseAlias(keyTypes, issuers);
}
@Override
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
return chooseAlias(new String[] { keyType }, issuers);
}
private String chooseAlias(String[] keyTypes, Principal[] issuers) {
if (keyTypes == null || keyTypes.length == 0) {
return null;
}
final X509Certificate cert = mChain[0];
final String certKeyAlg = cert.getPublicKey().getAlgorithm();
final String certSigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
for (String keyAlgorithm : keyTypes) {
if (keyAlgorithm == null) {
continue;
}
final String sigAlgorithm;
// handle cases like EC_EC and EC_RSA
int index = keyAlgorithm.indexOf('_');
if (index == -1) {
sigAlgorithm = null;
} else {
sigAlgorithm = keyAlgorithm.substring(index + 1);
keyAlgorithm = keyAlgorithm.substring(0, index);
}
// key algorithm does not match
if (!certKeyAlg.equals(keyAlgorithm)) {
continue;
}
/*
* TODO find a more reliable test for signature
* algorithm. Unfortunately value varies with
* provider. For example for "EC" it could be
* "SHA1WithECDSA" or simply "ECDSA".
*/
// sig algorithm does not match
if (sigAlgorithm != null && certSigAlg != null
&& !certSigAlg.contains(sigAlgorithm)) {
continue;
}
// no issuers to match
if (issuers == null || issuers.length == 0) {
return mAlias;
}
List<Principal> issuersList = Arrays.asList(issuers);
// check that a certificate in the chain was issued by one of the specified issuers
for (X509Certificate certFromChain : mChain) {
/*
* Note use of X500Principal from
* getIssuerX500Principal as opposed to Principal
* from getIssuerDN. Principal.equals test does
* not work in the case where
* xcertFromChain.getIssuerDN is a bouncycastle
* org.bouncycastle.jce.X509Principal.
*/
X500Principal issuerFromChain = certFromChain.getIssuerX500Principal();
if (issuersList.contains(issuerFromChain)) {
return mAlias;
}
}
Log.w(LOG_TAG, "Client certificate " + mAlias + " not issued by any of the requested issuers");
return null;
}
Log.w(LOG_TAG, "Client certificate " + mAlias + " does not match any of the requested key types");
return null;
}
}

Some files were not shown because too many files have changed in this diff Show More