From e75f230075b4059be6a9b6d27d8b6b202c74a6ff Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Mon, 4 Sep 2017 12:32:28 -0700 Subject: [PATCH] Enable HTTP authentication. Implements https://redmine.stoutner.com/issues/52. --- .idea/dictionaries/soren.xml | 1 + app/src/main/assets/de/about_licenses.html | 1 + app/src/main/assets/en/about_licenses.html | 1 + app/src/main/assets/en/images/ic_lock.png | Bin 0 -> 1646 bytes app/src/main/assets/es/about_licenses.html | 1 + app/src/main/assets/es/guide_user_agent.html | 4 +- app/src/main/assets/it/about_licenses.html | 1 + .../activities/MainWebViewActivity.java | 83 +++++-- .../dialogs/CreateBookmarkDialog.java | 8 +- .../dialogs/HttpAuthenticationDialog.java | 235 ++++++++++++++++++ .../PinnedSslCertificateMismatchDialog.java | 7 +- .../dialogs/SslCertificateErrorDialog.java | 3 +- app/src/main/res/drawable/lock_dark.xml | 13 + app/src/main/res/drawable/lock_light.xml | 13 + .../main/res/drawable/move_to_folder_dark.xml | 2 +- .../res/drawable/move_to_folder_light.xml | 2 +- .../ssl_certificate_disabled_dark.xml | 2 +- .../ssl_certificate_disabled_light.xml | 2 +- .../drawable/ssl_certificate_enabled_dark.xml | 2 +- .../ssl_certificate_enabled_light.xml | 2 +- .../res/layout/http_authentication_dialog.xml | 83 +++++++ app/src/main/res/values-es/strings.xml | 2 + app/src/main/res/values-it/strings.xml | 2 + app/src/main/res/values/strings.xml | 6 + 24 files changed, 438 insertions(+), 38 deletions(-) create mode 100644 app/src/main/assets/en/images/ic_lock.png create mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/HttpAuthenticationDialog.java create mode 100644 app/src/main/res/drawable/lock_dark.xml create mode 100644 app/src/main/res/drawable/lock_light.xml create mode 100644 app/src/main/res/layout/http_authentication_dialog.xml diff --git a/.idea/dictionaries/soren.xml b/.idea/dictionaries/soren.xml index f6ab929f..25a08015 100644 --- a/.idea/dictionaries/soren.xml +++ b/.idea/dictionaries/soren.xml @@ -92,6 +92,7 @@ roadmap robinlinus samsung + searx securitypatch sendto showsoftinput diff --git a/app/src/main/assets/de/about_licenses.html b/app/src/main/assets/de/about_licenses.html index 7899ba46..23c09ef9 100644 --- a/app/src/main/assets/de/about_licenses.html +++ b/app/src/main/assets/de/about_licenses.html @@ -102,6 +102,7 @@

ic_list.

ic_local_activity.

ic_location_off.

+

ic_lock.

ic_map.

ic_more.

ic_question_answer.

diff --git a/app/src/main/assets/en/about_licenses.html b/app/src/main/assets/en/about_licenses.html index cad98dc9..295123d5 100644 --- a/app/src/main/assets/en/about_licenses.html +++ b/app/src/main/assets/en/about_licenses.html @@ -96,6 +96,7 @@

ic_list.

ic_local_activity.

ic_location_off.

+

ic_lock.

ic_map.

ic_more.

ic_question_answer.

diff --git a/app/src/main/assets/en/images/ic_lock.png b/app/src/main/assets/en/images/ic_lock.png new file mode 100644 index 0000000000000000000000000000000000000000..dba1a27b0ae91b66e778255e44f6a54bdd8628c9 GIT binary patch literal 1646 zcmaKtYgCd47{}lD1yjLW0%?euOfu9mE6c!J-X?}Oyg-EtUNA!lFVik&(9F`bnwnZ^ zy0n#%A+;roR2p>4FjGNK#4UmM1e`_2?Lu7LU-l>fOFF40_}_M>@|oh zbAuVVG5a{V`Rwd8AU{8UeFmSKlfo9Ht>2fOeqqWR3jjv1DM8y~_t(9x-+h7}hd$nw zIUv1DO*MZ~%L$@-`s6wWm<0y6{?U4=*93u|)bn?F_{7cN(@bdZIo6DhRCj{!?y8al z8KHghcv9lP`rnnGBB{y+UPI0d=}<$5+8D_mm-YY7UE&xNjG1!7=lqI^CJV8X%lJlo z=sPe$QKX)ZfVx1r$X>BR6IEneE-VAbAy-kW;=|F~nEf`jgZi}1MMQr;-KL*iJL=zp ziQErctS=)Or?~xraSNej@U9W{Mhmsr`K0jc-p>Ny9lhgz8FHg3Bk(ZBZ$J&!oU9qJ zJ$pd}YU4hgSu}Z%sCi=BouF8*r5h0O0KHE7&V6?E{3W_N0?J2L4=X(9yR5oy4i80R zXT0{%B3U*%&kaqN8hEmcoe7F}>W6D!gCbvMBpb6#I4tTeEi7AJK>g75AY75G*?W4P z3G=orhW9vY04a+W%= z$>(=S?{$kkRaJzPwOg9y%{|W&iB?bfnZDbJ<1ZUV`xFGv9<^xxCtdlzymD!pGHe0G z9E*{-A(5(fd_Y0@gz-X!oER^h%U{7O!LVW`dzK{JtY%vh5LaX$h{p{?zm{x;DYOpvOEa^THJ7;V#g@zGE zqt*apwLn!06m`nff7`#nalEhQ<;D?f$*PcPeEqA*E0+7cVxtI?f8OQ0G~GLBz4e+G zP-lUOz2*%sD0aO%?R94b*}MSZ%^=B5b6{=X%u)EIyvC3CXQ?uq_DoV9?E>e^T|aU+ zcgUWQq?JRlb}Al!D3nA=v26Hm*aCjMg5U>Ba%!4j1o2gG>8p=Ews5q8%}ZN;`_`*y zSf*v}xkp>4pkCJp4wMh$T)k*eEn$?t37-U~L9+N>2`4Dqg#Zkr9lhycUT3ZSD{nV& z5%_ICJo5jq1^>0}-+tU&9OD$8>kxOW^~D)F*MPd zuUNZ$*kkv1X&+v3;EUI8Y41ca>itKQJf3K-@d1a)oU#OsvJ)TLvdf+id3ZMW@CgZm-B9P@|X)yQmMMh)SVXte3Wid z?!ap5jRR&;c}ewIiUr}P3P(CfRg}LvruASY`yaV~MhpNSvziXZt$d+BOW1~FVReas zpR4YLT$VNJ1_-YRYg`wWF3Z^LrqwNG#+E~SOMNVQU=B`J?dHbD+KiFygOS-UC0}RB z;!_U2e(~nfW@jBq6R5G*b4%sH-b)mmLZW>$8`o0_9h(igp zslYED;M#lt3q-KaMlW9ZOG&;1+qmnY$Q)<=8~5Ay8THh0uv?t-#8O6E0ls2$yZ zF!=#=%F6Ft_$_7YNLsg<##QV519671p@VuUW&+fpH0XDgb?56LHHh4i-VT>;@x6n7 x>hiuZlc-)>r&t$l2Na&hon+e<|08$mE&I89C=t73TMWMepae$*-3VYE{R?ow&fx$6 literal 0 HcmV?d00001 diff --git a/app/src/main/assets/es/about_licenses.html b/app/src/main/assets/es/about_licenses.html index 55df760e..633afd4e 100644 --- a/app/src/main/assets/es/about_licenses.html +++ b/app/src/main/assets/es/about_licenses.html @@ -101,6 +101,7 @@

ic_list.

ic_local_activity.

ic_location_off.

+

ic_lock.

ic_map.

ic_more.

ic_question_answer.

diff --git a/app/src/main/assets/es/guide_user_agent.html b/app/src/main/assets/es/guide_user_agent.html index f32e67d9..813eb5fb 100644 --- a/app/src/main/assets/es/guide_user_agent.html +++ b/app/src/main/assets/es/guide_user_agent.html @@ -75,6 +75,8 @@ Firefox o Chrome son los usuarios de agente más comunes, pero se actualizan automáticamente y sus números de versión cambian tan rápidamente que es probable que los usuarios de agente incluídos en Privacy Browser no estén ya en sintonía con la mayoría de agentes de usuario en los registros del servidor.

-

El WebView de android no permite que el agente de usuario esté en blanco. Si lo está, WebView simplemente envía el agente de usuario por defecto al servidor.

+

Algunas páginas webno funcionan correctamente si no reconocen el agente de usuario. + Usando la configuración de dominios para establecer el agente de usuario a WebView por defecto, o a otro agente de usuario que sea normalmente reconocido, suele resolver el problema. + El WebView de android no permite que el agente de usuario esté en blanco. Si lo está, WebView simplemente envía el agente de usuario por defecto al servidor.

\ No newline at end of file diff --git a/app/src/main/assets/it/about_licenses.html b/app/src/main/assets/it/about_licenses.html index 23ee075a..7e389967 100644 --- a/app/src/main/assets/it/about_licenses.html +++ b/app/src/main/assets/it/about_licenses.html @@ -104,6 +104,7 @@

ic_list.

ic_local_activity.

ic_location_off.

+

ic_lock.

ic_map.

ic_more.

ic_question_answer.

diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index e16901d0..b13a7852 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -74,6 +74,7 @@ import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.webkit.CookieManager; import android.webkit.DownloadListener; +import android.webkit.HttpAuthHandler; import android.webkit.SslErrorHandler; import android.webkit.WebBackForwardList; import android.webkit.WebChromeClient; @@ -95,6 +96,7 @@ import com.stoutner.privacybrowser.BuildConfig; import com.stoutner.privacybrowser.R; import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog; import com.stoutner.privacybrowser.dialogs.DownloadImageDialog; +import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog; import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog; import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog; import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog; @@ -121,11 +123,12 @@ import java.util.Set; // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21. public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener, - PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener { + HttpAuthenticationDialog.HttpAuthenticationListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener, + DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener { // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsListFragment`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, - // `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `MoveToFolderDialog`, `SslCertificateErrorDialog`, `UrlHistoryDialog`, `ViewSslCertificateDialog`, - // `CreateHomeScreenShortcutDialog`, and `OrbotProxyHelper`. It is also used in `onCreate()`, `applyAppSettings()`, `applyDomainSettings()`, and `updatePrivacyIcons()`. + // `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `HttpAuthenticationDialog`, `MoveToFolderDialog`, `SslCertificateErrorDialog`, `UrlHistoryDialog`, + // `ViewSslCertificateDialog`, `CreateHomeScreenShortcutDialog`, and `OrbotProxyHelper`. It is also used in `onCreate()`, `applyAppSettings()`, `applyDomainSettings()`, and `updatePrivacyIcons()`. public static boolean darkTheme; // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, @@ -303,6 +306,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`. private SslErrorHandler sslErrorHandler; + // `httpAuthHandler` is used in `onCreate()`, `onHttpAuthenticationCancel()`, and `onHttpAuthenticationProceed()`. + private static HttpAuthHandler httpAuthHandler; + // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`. private InputMethodManager inputMethodManager; @@ -729,6 +735,17 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } } + // Handle HTTP authentication requests. + @Override + public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { + // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`. + httpAuthHandler = handler; + + // Display the HTTP authentication dialog. + AppCompatDialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm); + httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication)); + } + // Update the URL in urlTextBox when the page starts to load. @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { @@ -867,7 +884,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) { // The pinned SSL certificate doesn't match the current domain certificate. //Display the pinned SSL certificate mismatch `AlertDialog`. AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog(); - pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_mismatch)); + pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_mismatch)); } } } @@ -902,7 +919,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Display the SSL error `AlertDialog`. AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error); - sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error)); + sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error)); } } }); @@ -1005,7 +1022,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { // Show the `DownloadFileDialog` `AlertDialog` and name this instance `@string/download`. AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength); - downloadFileDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download)); + downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); } }); @@ -1260,47 +1277,47 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Prepare the font size title and current size menu item. switch (fontSize) { case 25: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.twenty_five_percent); + fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent); selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent); break; case 50: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent); + fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent); selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent); break; case 75: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent); + fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent); selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent); break; case 100: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent); + fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent); selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent); break; case 125: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent); + fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent); selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent); break; case 150: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent); + fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent); selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent); break; case 175: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent); + fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent); selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent); break; case 200: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent); + fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent); selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent); break; default: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent); + fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent); selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent); break; } @@ -1631,13 +1648,13 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter(); // Print the document. The print attributes are `null`. - printManager.print(getResources().getString(R.string.privacy_browser_web_page), printDocumentAdapter, null); + printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null); return true; case R.id.add_to_homescreen: // Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`. AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog(); - createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.create_shortcut)); + createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut)); //Everything else will be handled by `CreateHomeScreenShortcutDialog` and the associated listener below. return true; @@ -1689,7 +1706,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Show the `UrlHistoryDialog` `AlertDialog` and name this instance `R.string.history`. `this` is the `Context`. AppCompatDialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList); - urlHistoryDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.history)); + urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history)); break; case R.id.bookmarks: @@ -1921,7 +1938,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation @Override public boolean onMenuItemClick(MenuItem item) { // Save the link URL in a `ClipData`. - ClipData srcAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), linkUrl); + ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl); // Set the `ClipData` as the clipboard's primary clip. clipboardManager.setPrimaryClip(srcAnchorTypeClipData); @@ -1964,7 +1981,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation @Override public boolean onMenuItemClick(MenuItem item) { // Save the email address in a `ClipData`. - ClipData srcEmailTypeClipData = ClipData.newPlainText(getResources().getString(R.string.email_address), linkUrl); + ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl); // Set the `ClipData` as the clipboard's primary clip. clipboardManager.setPrimaryClip(srcEmailTypeClipData); @@ -1999,7 +2016,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation public boolean onMenuItemClick(MenuItem item) { // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`. AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); - downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download)); + downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); return false; } }); @@ -2009,7 +2026,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation @Override public boolean onMenuItemClick(MenuItem item) { // Save the image URL in a `ClipData`. - ClipData srcImageTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl); + ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl); // Set the `ClipData` as the clipboard's primary clip. clipboardManager.setPrimaryClip(srcImageTypeClipData); @@ -2045,7 +2062,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation public boolean onMenuItemClick(MenuItem item) { // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`. AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); - downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download)); + downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); return false; } }); @@ -2055,7 +2072,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation @Override public boolean onMenuItemClick(MenuItem item) { // Save the image URL in a `ClipData`. - ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl); + ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl); // Set the `ClipData` as the clipboard's primary clip. clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData); @@ -2183,10 +2200,26 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } } + @Override + public void onHttpAuthenticationCancel() { + // Cancel the `HttpAuthHandler`. + httpAuthHandler.cancel(); + } + + @Override + public void onHttpAuthenticationProceed(AppCompatDialogFragment dialogFragment) { + // Get handles for the `EditTexts`. + EditText usernameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.http_authentication_username); + EditText passwordEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.http_authentication_password); + + // Proceed with the HTTP authentication. + httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString()); + } + public void viewSslCertificate(View view) { // Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`. DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog(); - viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate)); + viewSslCertificateDialogFragment.show(getFragmentManager(), getString(R.string.view_ssl_certificate)); } @Override diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.java index 60863a9c..b2966c14 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.java @@ -96,7 +96,7 @@ public class CreateBookmarkDialog extends AppCompatDialogFragment { dialogBuilder.setPositiveButton(R.string.create, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - // Return the `DialogFragment` to the parent activity on create. + // Return the `DialogFragment` to the parent activity. createBookmarkListener.onCreateBookmark(CreateBookmarkDialog.this); } }); @@ -111,7 +111,7 @@ public class CreateBookmarkDialog extends AppCompatDialogFragment { // Show the keyboard when the `AlertDialog` is displayed on the screen. alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - // We need to show the `AlertDialog` before we can call `setOnKeyListener()` below. + // The `AlertDialog` needs to be shown before `setOnKeyListener()` can be called. alertDialog.show(); // Get a handle for `create_bookmark_name_edittext`. @@ -127,8 +127,10 @@ public class CreateBookmarkDialog extends AppCompatDialogFragment { if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) { // Trigger `createBookmarkListener` and return the `DialogFragment` to the parent activity. createBookmarkListener.onCreateBookmark(CreateBookmarkDialog.this); + // Manually dismiss the `AlertDialog`. alertDialog.dismiss(); + // Consume the event. return true; } else { // If any other key was pressed, do not consume the event. @@ -148,8 +150,10 @@ public class CreateBookmarkDialog extends AppCompatDialogFragment { if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) { // Trigger `createBookmarkListener` and return the DialogFragment to the parent activity. createBookmarkListener.onCreateBookmark(CreateBookmarkDialog.this); + // Manually dismiss the `AlertDialog`. alertDialog.dismiss(); + // Consume the event. return true; } else { // If any other key was pressed, do not consume the event. diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/HttpAuthenticationDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/HttpAuthenticationDialog.java new file mode 100644 index 00000000..993c8c3d --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/HttpAuthenticationDialog.java @@ -0,0 +1,235 @@ +/* + * Copyright © 2017 Soren Stoutner . + * + * This file is part of Privacy Browser . + * + * Privacy Browser is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser. If not, see . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +// `AppCompatDialogFragment` is used instead of `DialogFragment` to avoid an error on API <=22. +import android.support.v7.app.AppCompatDialogFragment; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.TextView; + +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.activities.MainWebViewActivity; + +public class HttpAuthenticationDialog extends AppCompatDialogFragment{ + + // The private variables are used in `onCreate()` and `onCreateDialog()`. + private String httpAuthHost; + private String httpAuthRealm; + + public static HttpAuthenticationDialog displayDialog(String host, String realm) { + // Store the strings in a `Bundle`. + Bundle argumentsBundle = new Bundle(); + argumentsBundle.putString("Host", host); + argumentsBundle.putString("Realm", realm); + + // Add `argumentsBundle` to this instance of `HttpAuthenticationDialog`. + HttpAuthenticationDialog thisHttpAuthenticationDialog = new HttpAuthenticationDialog(); + thisHttpAuthenticationDialog.setArguments(argumentsBundle); + return thisHttpAuthenticationDialog; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Save the host and realm in class variables. + httpAuthHost = getArguments().getString("Host"); + httpAuthRealm = getArguments().getString("Realm"); + } + + // The public interface is used to send information back to the parent activity. + public interface HttpAuthenticationListener { + void onHttpAuthenticationCancel(); + + void onHttpAuthenticationProceed(AppCompatDialogFragment dialogFragment); + } + + // `httpAuthenticationListener` is used in `onAttach()` and `onCreateDialog()` + private HttpAuthenticationListener httpAuthenticationListener; + + public void onAttach(Context context) { + super.onAttach(context); + + // Get a handle for `httpAuthenticationListener` from `context`. + try { + httpAuthenticationListener = (HttpAuthenticationListener) context; + } catch(ClassCastException exception) { + throw new ClassCastException(context.toString() + " must implement `HttpAuthenticationListener`."); + } + } + + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Get the activity's layout inflater. + LayoutInflater layoutInflater = getActivity().getLayoutInflater(); + + // Use `AlertDialog.Builder` to create the `AlertDialog`. + AlertDialog.Builder dialogBuilder; + + // Set the style according to the theme. + if (MainWebViewActivity.darkTheme) { + // Set the dialog theme. + dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark); + + // Set the icon. + dialogBuilder.setIcon(R.drawable.lock_dark); + } else { + // Set the dialog theme. + dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight); + + // Set the icon. + dialogBuilder.setIcon(R.drawable.lock_light); + } + + // Set the title. + dialogBuilder.setTitle(R.string.http_authentication); + + // Set the layout. The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(layoutInflater.inflate(R.layout.http_authentication_dialog, null)); + + // Setup the negative button. + dialogBuilder.setNegativeButton(R.string.close, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Call `onHttpAuthenticationCancel()` and return the `DialogFragment` to the parent activity. + httpAuthenticationListener.onHttpAuthenticationCancel(); + } + }); + + // Setup the positive button. + dialogBuilder.setPositiveButton(R.string.proceed, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Call `onHttpAuthenticationProceed()` and return the `DialogFragment` to the parent activity. + httpAuthenticationListener.onHttpAuthenticationProceed(HttpAuthenticationDialog.this); + } + }); + + // Create an `AlertDialog` from the `AlertDialog.Builder`. + final AlertDialog alertDialog = dialogBuilder.create(); + + // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. + assert alertDialog.getWindow() != null; + + // Show the keyboard when the `AlertDialog` is displayed on the screen. + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + + // The `AlertDialog` needs to be shown before `setOnKeyListener()` can be called. + alertDialog.show(); + + // Get handles for the views in `alertDialog`. + TextView realmTextView = (TextView) alertDialog.findViewById(R.id.http_authentication_realm); + TextView hostTextView = (TextView) alertDialog.findViewById(R.id.http_authentication_host); + EditText usernameEditText = (EditText) alertDialog.findViewById(R.id.http_authentication_username); + EditText passwordEditText = (EditText) alertDialog.findViewById(R.id.http_authentication_password); + + // Set the realm text. + realmTextView.setText(httpAuthRealm); + + // Set the realm text color according to the theme. The deprecated `.getColor()` must be used until API >= 23. + if (MainWebViewActivity.darkTheme) { + //noinspection deprecation + realmTextView.setTextColor(getResources().getColor(R.color.gray_300)); + } else { + //noinspection deprecation + realmTextView.setTextColor(getResources().getColor(R.color.black)); + } + + // Initialize the host label and the `SpannableStringBuilder`. + String hostLabel = getString(R.string.host) + " "; + SpannableStringBuilder hostStringBuilder = new SpannableStringBuilder(hostLabel + httpAuthHost); + + // Create a blue `ForegroundColorSpan`. + ForegroundColorSpan blueColorSpan; + + // Set `blueColorSpan` according to the theme. The deprecated `getColor()` must be used until API >= 23. + if (MainWebViewActivity.darkTheme) { + //noinspection deprecation + blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_400)); + } else { + //noinspection deprecation + blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700)); + } + + // Setup the span to display the host name in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + hostStringBuilder.setSpan(blueColorSpan, hostLabel.length(), hostStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + // Set the host text. + hostTextView.setText(hostStringBuilder); + + // Allow the `enter` key on the keyboard to trigger `onHttpAuthenticationProceed` from `usernameEditText`. + usernameEditText.setOnKeyListener(new View.OnKeyListener() { + public boolean onKey(View view, int keyCode, KeyEvent event) { + // If the event is a key-down on the `enter` key, call `onHttpAuthenticationProceed()`. + if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) { + // Trigger `onHttpAuthenticationProceed` and return the `DialogFragment` to the parent activity. + httpAuthenticationListener.onHttpAuthenticationProceed(HttpAuthenticationDialog.this); + + // Manually dismiss the `AlertDialog`. + alertDialog.dismiss(); + + // Consume the event. + return true; + } else { // If any other key was pressed, do not consume the event. + return false; + } + } + }); + + // Allow the `enter` key on the keyboard to trigger `onHttpAuthenticationProceed()` from `passwordEditText`. + passwordEditText.setOnKeyListener(new View.OnKeyListener() { + public boolean onKey(View view, int keyCode, KeyEvent event) { + // If the event is a key-down on the `enter` key, call `onHttpAuthenticationProceed()`. + if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) { + // Trigger `onHttpAuthenticationProceed` and return the `DialogFragment` to the parent activity. + httpAuthenticationListener.onHttpAuthenticationProceed(HttpAuthenticationDialog.this); + + // Manually dismiss the `AlertDialog`. + alertDialog.dismiss(); + + // Consume the event. + return true; + } else { // If any other key was pressed, do not consume the event. + return false; + } + } + }); + + // `onCreateDialog()` requires the return of an `AlertDialog`. + return alertDialog; + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java index aa5b6279..d3315ad1 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java @@ -29,7 +29,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.TabLayout; import android.support.v4.view.PagerAdapter; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. +// `AppCompatDialogFragment` is used instead of `DialogFragment` to avoid an error on API <=22. import android.support.v7.app.AppCompatDialogFragment; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -47,8 +47,6 @@ import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; import java.text.DateFormat; import java.util.Date; -// `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. -@SuppressLint("InflateParams") public class PinnedSslCertificateMismatchDialog extends AppCompatDialogFragment { // `layoutInflater` is used in `onCreateDialog()` and `pagerAdapter`. private LayoutInflater layoutInflater; @@ -84,6 +82,9 @@ public class PinnedSslCertificateMismatchDialog extends AppCompatDialogFragment } } + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + @Override @NonNull public Dialog onCreateDialog(Bundle savedInstanceState) { // Get the activity's layout inflater. diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java index e966b2ec..00a3b1f9 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java @@ -28,7 +28,7 @@ import android.net.http.SslCertificate; import android.net.http.SslError; import android.os.Bundle; import android.support.annotation.NonNull; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22. +// `AppCompatDialogFragment` is used instead of `DialogFragment` to avoid an error on API <=22. import android.support.v7.app.AppCompatDialogFragment; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -44,6 +44,7 @@ import java.util.Date; public class SslCertificateErrorDialog extends AppCompatDialogFragment { + // The private variables are used in `onCreate()` and `onCreateDialog()`. private int primaryErrorInt; private String urlWithError; private String issuedToCName; diff --git a/app/src/main/res/drawable/lock_dark.xml b/app/src/main/res/drawable/lock_dark.xml new file mode 100644 index 00000000..9453cee8 --- /dev/null +++ b/app/src/main/res/drawable/lock_dark.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/lock_light.xml b/app/src/main/res/drawable/lock_light.xml new file mode 100644 index 00000000..3c8b86a3 --- /dev/null +++ b/app/src/main/res/drawable/lock_light.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/move_to_folder_dark.xml b/app/src/main/res/drawable/move_to_folder_dark.xml index 16dc955b..b893e402 100644 --- a/app/src/main/res/drawable/move_to_folder_dark.xml +++ b/app/src/main/res/drawable/move_to_folder_dark.xml @@ -11,7 +11,7 @@ android:autoMirrored="true" tools:ignore="VectorRaster" > - + diff --git a/app/src/main/res/drawable/move_to_folder_light.xml b/app/src/main/res/drawable/move_to_folder_light.xml index 92e9b197..98ec8c2d 100644 --- a/app/src/main/res/drawable/move_to_folder_light.xml +++ b/app/src/main/res/drawable/move_to_folder_light.xml @@ -11,7 +11,7 @@ android:autoMirrored="true" tools:ignore="VectorRaster" > - + diff --git a/app/src/main/res/drawable/ssl_certificate_disabled_dark.xml b/app/src/main/res/drawable/ssl_certificate_disabled_dark.xml index 3f6998c1..425c3720 100644 --- a/app/src/main/res/drawable/ssl_certificate_disabled_dark.xml +++ b/app/src/main/res/drawable/ssl_certificate_disabled_dark.xml @@ -11,7 +11,7 @@ android:autoMirrored="true" tools:ignore="VectorRaster" > - + diff --git a/app/src/main/res/drawable/ssl_certificate_disabled_light.xml b/app/src/main/res/drawable/ssl_certificate_disabled_light.xml index c8010697..e3deaaab 100644 --- a/app/src/main/res/drawable/ssl_certificate_disabled_light.xml +++ b/app/src/main/res/drawable/ssl_certificate_disabled_light.xml @@ -11,7 +11,7 @@ android:autoMirrored="true" tools:ignore="VectorRaster" > - + diff --git a/app/src/main/res/drawable/ssl_certificate_enabled_dark.xml b/app/src/main/res/drawable/ssl_certificate_enabled_dark.xml index ba20d431..930b9f47 100644 --- a/app/src/main/res/drawable/ssl_certificate_enabled_dark.xml +++ b/app/src/main/res/drawable/ssl_certificate_enabled_dark.xml @@ -11,7 +11,7 @@ android:autoMirrored="true" tools:ignore="VectorRaster" > - + diff --git a/app/src/main/res/drawable/ssl_certificate_enabled_light.xml b/app/src/main/res/drawable/ssl_certificate_enabled_light.xml index 9fc00fe8..ee0a73fc 100644 --- a/app/src/main/res/drawable/ssl_certificate_enabled_light.xml +++ b/app/src/main/res/drawable/ssl_certificate_enabled_light.xml @@ -11,7 +11,7 @@ android:autoMirrored="true" tools:ignore="VectorRaster" > - + diff --git a/app/src/main/res/layout/http_authentication_dialog.xml b/app/src/main/res/layout/http_authentication_dialog.xml new file mode 100644 index 00000000..df262c55 --- /dev/null +++ b/app/src/main/res/layout/http_authentication_dialog.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ec864095..a76556dc 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -63,6 +63,8 @@ La comunicación con esta página web no está cifrada. Esto permite a terceras partes interceptar información, rastrear su navegación e inyectar contenido malicioso. Certificado SSL Cerrar + Dominio + Dominio: Emitido a Emitido por Nombre Común (CN): diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 443ab4cd..9ed001fd 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -65,6 +65,8 @@ La comunicazione con questo sito web non è criptata. Questo consente a terze parti la possibilità di intercettare le informazioni scambiate, di tracciare la navigazione e di inviare contenuto maligno. Certificato SSL Chiudi + Dominio + Dominio: Emesso a Emesso da Nome comune (NC): diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cdd45310..c15727c5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -96,6 +96,12 @@ Current SSL Pinned SSL + + HTTP Authentication + Host: + Username + Password + Navigation Drawer Navigation -- 2.45.2