816 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			816 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using System.Net;
 | |
| #if !NoNet
 | |
| using FluentFTP;
 | |
| #endif
 | |
| using System.Text;
 | |
| 
 | |
| using Android.App;
 | |
| using Android.Content;
 | |
| using Android.OS;
 | |
| using Android.Runtime;
 | |
| using Android.Views;
 | |
| using Android.Widget;
 | |
| using Google.Android.Material.Dialog;
 | |
| using Java.IO;
 | |
| using keepass2android.Io;
 | |
| using keepass2android;
 | |
| #if !EXCLUDE_JAVAFILESTORAGE
 | |
| using Keepass2android.Javafilestorage;
 | |
| #endif
 | |
| using KeePassLib.Serialization;
 | |
| using KeePassLib.Utility;
 | |
| 
 | |
| namespace keepass2android
 | |
| {
 | |
| 	public class FileSelectHelper
 | |
| 	{
 | |
| 		public static string ConvertFilenameToIocPath(string filename)
 | |
| 		{
 | |
| 			if ((filename != null) && (filename.StartsWith("file://")))
 | |
| 			{
 | |
| 				filename = filename.Substring(7);
 | |
| 				filename = Java.Net.URLDecoder.Decode(filename);
 | |
| 			}
 | |
| 			return filename;
 | |
| 		}
 | |
| 		
 | |
| 		private readonly Activity _activity;
 | |
| 		private readonly bool _isForSave;
 | |
| 		private readonly int _requestCode;
 | |
| 	    private readonly string _schemeSeparator = "://";
 | |
| 	    private bool _tryGetPermanentAccess;
 | |
| 
 | |
| 		private const int SftpModeSpinnerPasswd = 0;
 | |
| 		private const int SftpModeSpinnerPubKey = 1;
 | |
| 		private const int SftpModeSpinnerCustomKey = 2;
 | |
| 
 | |
| 		private const int SftpKeySpinnerCreateNewIdx = 0;
 | |
| 
 | |
| 	    public string DefaultExtension { get; set; }
 | |
| 
 | |
| 		public FileSelectHelper(Activity activity, bool isForSave, bool tryGetPermanentAccess, int requestCode)
 | |
| 		{
 | |
| 			_activity = activity;
 | |
| 			_isForSave = isForSave;
 | |
| 			_requestCode = requestCode;
 | |
| 			_tryGetPermanentAccess = tryGetPermanentAccess;
 | |
| 		}
 | |
| 
 | |
| 		private void ShowSftpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
 | |
| 		{
 | |
| #if !EXCLUDE_JAVAFILESTORAGE && !NoNet
 | |
| 			MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
 | |
| 			View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.sftpcredentials, null);
 | |
| 			var ctx = activity.ApplicationContext;
 | |
|             var fileStorage = new Keepass2android.Javafilestorage.SftpStorage(ctx);
 | |
| 
 | |
|             LinearLayout addNewBtn = dlgContents.FindViewById<LinearLayout>(Resource.Id.sftp_add_key_group);
 | |
|             Button deleteBtn = dlgContents.FindViewById<Button>(Resource.Id.sftp_delete_key_button);
 | |
|             EditText keyNameTxt = dlgContents.FindViewById<EditText>(Resource.Id.sftp_key_name);
 | |
|             EditText keyContentTxt = dlgContents.FindViewById<EditText>(Resource.Id.sftp_key_content);
 | |
| 
 | |
|             var keySpinner = dlgContents.FindViewById<Spinner>(Resource.Id.sftp_key_names);
 | |
| 			var keyNamesAdapter = new ArrayAdapter(ctx, Android.Resource.Layout.SimpleSpinnerDropDownItem, new List<string>());
 | |
| 			UpdatePrivateKeyNames(keyNamesAdapter, fileStorage, ctx);
 | |
| 			keyNamesAdapter.SetDropDownViewResource(Android.Resource.Layout.SimpleSpinnerDropDownItem);
 | |
| 			keySpinner.Adapter = keyNamesAdapter;
 | |
| 			keySpinner.SetSelection(SftpKeySpinnerCreateNewIdx);
 | |
| 
 | |
| 			keySpinner.ItemSelected += (sender, args) =>
 | |
| 			{
 | |
|                 if (keySpinner.SelectedItemPosition == SftpKeySpinnerCreateNewIdx)
 | |
| 				{
 | |
| 					keyNameTxt.Text = "";
 | |
| 					keyContentTxt.Text = "";
 | |
| 					addNewBtn.Visibility = ViewStates.Visible;
 | |
| 					deleteBtn.Visibility = ViewStates.Gone;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					addNewBtn.Visibility = ViewStates.Gone;
 | |
| 					deleteBtn.Visibility = ViewStates.Visible;
 | |
| 				}
 | |
| 			};
 | |
| 
 | |
|             var authModeSpinner = dlgContents.FindViewById<Spinner>(Resource.Id.sftp_auth_mode_spinner);
 | |
| 			dlgContents.FindViewById<Button>(Resource.Id.send_public_key_button).Click += (sender, args) =>
 | |
| 			{
 | |
|                 string pub_filename = fileStorage.CreateKeyPair();
 | |
| 
 | |
| 				Intent sendIntent = new Intent();
 | |
| 				sendIntent.SetAction(Intent.ActionSend);
 | |
| 				sendIntent.PutExtra(Intent.ExtraText, System.IO.File.ReadAllText(pub_filename));
 | |
| 
 | |
| 				sendIntent.PutExtra(Intent.ExtraSubject, "Keepass2Android sftp public key");
 | |
| 				sendIntent.SetType("text/plain");
 | |
| 				activity.StartActivity(Intent.CreateChooser(sendIntent, "Send public key to..."));
 | |
| 			};
 | |
| 
 | |
| 			dlgContents.FindViewById<Button>(Resource.Id.sftp_save_key_button).Click += (sender, args) =>
 | |
| 			{
 | |
| 				string keyName = keyNameTxt.Text;
 | |
| 				string keyContent = keyContentTxt.Text;
 | |
| 
 | |
| 				string toastMsg = null;
 | |
|                 if (!string.IsNullOrEmpty(keyName) && !string.IsNullOrEmpty(keyContent))
 | |
| 				{
 | |
| 					try
 | |
| 					{
 | |
| 						fileStorage.SavePrivateKeyContent(keyName, keyContent);
 | |
|                         keyNameTxt.Text = "";
 | |
| 						keyContentTxt.Text = "";
 | |
|                         toastMsg = ctx.GetString(Resource.String.private_key_saved);
 | |
|                     }
 | |
| 					catch (Exception e)
 | |
| 					{
 | |
| 						toastMsg = ctx.GetString(Resource.String.private_key_save_failed,
 | |
| 							new Java.Lang.Object[] { e.Message });
 | |
| 					}
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					toastMsg = ctx.GetString(Resource.String.private_key_info);
 | |
| 				}
 | |
| 
 | |
| 				if (toastMsg!= null) {
 | |
|                     Toast.MakeText(_activity, toastMsg, ToastLength.Long).Show();
 | |
|                 }
 | |
| 
 | |
| 				UpdatePrivateKeyNames(keyNamesAdapter, fileStorage, ctx);
 | |
| 				keySpinner.SetSelection(ResolveKeySpinnerSelection(keyNamesAdapter, keyName));
 | |
| 			};
 | |
| 
 | |
| 			dlgContents.FindViewById<Button>(Resource.Id.sftp_delete_key_button).Click += (sender, args) =>
 | |
| 			{
 | |
| 				int selectedKey = dlgContents.FindViewById<Spinner>(Resource.Id.sftp_key_names).SelectedItemPosition;
 | |
| 				string keyName = ResolveSelectedKeyName(keyNamesAdapter, selectedKey);
 | |
| 				if (!string.IsNullOrEmpty(keyName))
 | |
| 				{
 | |
| 					bool deleted = fileStorage.DeleteCustomKey(keyName);
 | |
| 
 | |
| 					int msgId = deleted ? Resource.String.private_key_delete : Resource.String.private_key_delete_failed;
 | |
| 					string msg = ctx.GetString(msgId, new Java.Lang.Object[] { keyName });
 | |
|                     Toast.MakeText(_activity, msg, ToastLength.Long).Show();
 | |
| 
 | |
| 					UpdatePrivateKeyNames(keyNamesAdapter, fileStorage, ctx);
 | |
| 					keySpinner.SetSelection(SftpKeySpinnerCreateNewIdx);
 | |
| 				}
 | |
| 			};
 | |
| 
 | |
| 			authModeSpinner.ItemSelected += (sender, args) =>
 | |
| 		    {
 | |
| 				var passwordBox = dlgContents.FindViewById<EditText>(Resource.Id.sftp_password);
 | |
| 				var publicKeyButton = dlgContents.FindViewById<Button>(Resource.Id.send_public_key_button);
 | |
| 				var keyfileGroup = dlgContents.FindViewById<LinearLayout>(Resource.Id.sftp_keyfile_group);
 | |
| 
 | |
|                 switch (authModeSpinner.SelectedItemPosition)
 | |
| 			    {
 | |
| 				    case SftpModeSpinnerPasswd:
 | |
| 					    passwordBox.Visibility = ViewStates.Visible;
 | |
| 					    publicKeyButton.Visibility = ViewStates.Gone;
 | |
| 					    keyfileGroup.Visibility = ViewStates.Gone;
 | |
| 					    break;
 | |
| 				    case SftpModeSpinnerPubKey:
 | |
| 					    passwordBox.Visibility = ViewStates.Gone;
 | |
| 					    publicKeyButton.Visibility = ViewStates.Visible;
 | |
| 					    keyfileGroup.Visibility = ViewStates.Gone;
 | |
| 					    break;
 | |
| 				    case SftpModeSpinnerCustomKey:
 | |
| 					    passwordBox.Visibility = ViewStates.Gone;
 | |
| 					    publicKeyButton.Visibility = ViewStates.Gone;
 | |
| 					    keyfileGroup.Visibility = ViewStates.Visible;
 | |
| 					    break;
 | |
| 			    }
 | |
| 		    };
 | |
| 
 | |
|             if (!defaultPath.EndsWith(_schemeSeparator))
 | |
| 		    {
 | |
|                 SftpStorage.ConnectionInfo ci = fileStorage.SplitStringToConnectionInfo(defaultPath);
 | |
| 		        dlgContents.FindViewById<EditText>(Resource.Id.sftp_host).Text = ci.Host;
 | |
| 		        dlgContents.FindViewById<EditText>(Resource.Id.sftp_port).Text = ci.Port.ToString();
 | |
| 		        dlgContents.FindViewById<EditText>(Resource.Id.sftp_user).Text = ci.Username;
 | |
| 		        dlgContents.FindViewById<EditText>(Resource.Id.sftp_password).Text = ci.Password;
 | |
| 				dlgContents.FindViewById<EditText>(Resource.Id.sftp_key_name).Text = ci.KeyName;
 | |
| 				dlgContents.FindViewById<EditText>(Resource.Id.sftp_key_passphrase).Text = ci.KeyPassphrase;
 | |
| 		        dlgContents.FindViewById<EditText>(Resource.Id.sftp_initial_dir).Text = ci.LocalPath;
 | |
| 		        if (ci.ConnectTimeoutSec != SftpStorage.UnsetSftpConnectTimeout)
 | |
| 		        {
 | |
| 			        dlgContents.FindViewById<EditText>(Resource.Id.sftp_connect_timeout).Text = ci.ConnectTimeoutSec.ToString();
 | |
| 		        }
 | |
| 				
 | |
| 				if (ci.ConfigOpts.Contains(SftpStorage.SshCfgKex))
 | |
| 				{
 | |
| 					dlgContents.FindViewById<EditText>(Resource.Id.sftp_kex).Text = ci.ConfigOpts[SftpStorage.SshCfgKex].ToString();
 | |
| 				}
 | |
|                 if (ci.ConfigOpts.Contains(SftpStorage.SshCfgServerHostKey))
 | |
|                 {
 | |
|                     dlgContents.FindViewById<EditText>(Resource.Id.sftp_shk).Text = ci.ConfigOpts[SftpStorage.SshCfgServerHostKey].ToString();
 | |
|                 }
 | |
| 
 | |
|                 if (!string.IsNullOrEmpty(ci.Password))
 | |
| 				{
 | |
| 					authModeSpinner.SetSelection(SftpModeSpinnerPasswd);
 | |
| 				} else if (!string.IsNullOrEmpty(ci.KeyName))
 | |
| 				{
 | |
| 					authModeSpinner.SetSelection(SftpModeSpinnerCustomKey);
 | |
| 					keySpinner.SetSelection(ResolveKeySpinnerSelection(keyNamesAdapter, ci.KeyName));
 | |
| 				} else
 | |
| 				{
 | |
| 					authModeSpinner.SetSelection(SftpModeSpinnerPubKey);
 | |
| 				}
 | |
| 
 | |
|             }
 | |
| 
 | |
| 			builder.SetView(dlgContents);
 | |
|             builder.SetPositiveButton(Android.Resource.String.Ok, (sender, args) => {
 | |
| 				int idx = 0;
 | |
|                 string host = dlgContents.FindViewById<EditText>(Resource.Id.sftp_host).Text;
 | |
|                 string portText = dlgContents.FindViewById<EditText>(Resource.Id.sftp_port).Text;
 | |
|                 int port = SftpStorage.DefaultSftpPort;
 | |
|                 if (!string.IsNullOrEmpty(portText))
 | |
|                     int.TryParse(portText, out port);
 | |
|                 string user = dlgContents.FindViewById<EditText>(Resource.Id.sftp_user).Text;
 | |
| 
 | |
|                 string password = null;
 | |
|                 string keyName = null, keyPassphrase = null;
 | |
|                 switch (dlgContents.FindViewById<Spinner>(Resource.Id.sftp_auth_mode_spinner).SelectedItemPosition)
 | |
|                 {
 | |
|                     case SftpModeSpinnerPasswd:
 | |
|                         password = dlgContents.FindViewById<EditText>(Resource.Id.sftp_password).Text;
 | |
|                         break;
 | |
|                     case SftpModeSpinnerPubKey:
 | |
|                         break;
 | |
|                     case SftpModeSpinnerCustomKey:
 | |
| 						keyName = ResolveSelectedKeyName(keyNamesAdapter, keySpinner.SelectedItemPosition);
 | |
| 						keyPassphrase = dlgContents.FindViewById<EditText>(Resource.Id.sftp_key_passphrase).Text;
 | |
|                         break;
 | |
|                 }
 | |
| 
 | |
|                 string initialPath = dlgContents.FindViewById<EditText>(Resource.Id.sftp_initial_dir).Text;
 | |
|                 if (string.IsNullOrEmpty(initialPath))
 | |
|                     initialPath = "/";
 | |
|                 string connectTimeoutText = dlgContents.FindViewById<EditText>(Resource.Id.sftp_connect_timeout).Text;
 | |
|                 int connectTimeout = SftpStorage.UnsetSftpConnectTimeout;
 | |
|                 if (!string.IsNullOrEmpty(connectTimeoutText))
 | |
|                 {
 | |
|                     int.TryParse(connectTimeoutText, out connectTimeout);
 | |
|                 }
 | |
| 				string kexAlgorithms = dlgContents.FindViewById<EditText>(Resource.Id.sftp_kex).Text;
 | |
| 				string shkAlgorithms = dlgContents.FindViewById<EditText>(Resource.Id.sftp_shk).Text;
 | |
| 
 | |
|                 string sftpPath = fileStorage.BuildFullPath(
 | |
|                     host, port, initialPath, user, password, connectTimeout, keyName, keyPassphrase,
 | |
| 					kexAlgorithms, shkAlgorithms);
 | |
| 
 | |
|                 onStartBrowse(sftpPath);
 | |
|             });
 | |
|             EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>((sender, e) => onCancel());
 | |
| 
 | |
| 			builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
 | |
| 			builder.SetTitle(activity.GetString(Resource.String.enter_sftp_login_title));
 | |
| 			Dialog dialog = builder.Create();
 | |
| 
 | |
| 			dialog.Show();
 | |
| #endif
 | |
|         }
 | |
| 
 | |
| #if !EXCLUDE_JAVAFILESTORAGE && !NoNet
 | |
|         private void UpdatePrivateKeyNames(ArrayAdapter dataView, SftpStorage storage, Context ctx)
 | |
|         {
 | |
| 			dataView.Clear();
 | |
| 			dataView.Add(ctx.GetString(Resource.String.private_key_create_new));
 | |
| 			foreach (string keyName in storage.GetCustomKeyNames()) 
 | |
| 				dataView.Add(keyName);
 | |
|         }
 | |
| 
 | |
|         private int ResolveKeySpinnerSelection(ArrayAdapter dataView, string keyName)
 | |
|         {
 | |
| 			int idx = -1;
 | |
| 			for (int i = 0; i < dataView.Count; i++)
 | |
| 			{
 | |
| 				string itemName = dataView.GetItem(i).ToString();
 | |
| 				if (string.Equals(keyName, itemName)) {
 | |
| 					idx = i;
 | |
| 					break;
 | |
| 				}
 | |
| 			}
 | |
| 			return idx < 0 ? SftpKeySpinnerCreateNewIdx : idx;
 | |
|         }
 | |
| 
 | |
|         private string ResolveSelectedKeyName(ArrayAdapter dataView, int selectedItem)
 | |
|         {
 | |
|             if (selectedItem != SftpKeySpinnerCreateNewIdx && selectedItem > 0 && selectedItem < dataView.Count)
 | |
|                 return dataView.GetItem(selectedItem).ToString();
 | |
|             else
 | |
|                 return null;
 | |
|         }
 | |
| #endif
 | |
| 
 | |
|         private void ShowHttpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
 | |
| 		{
 | |
| #if !EXCLUDE_JAVAFILESTORAGE && !NoNet
 | |
| 			MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
 | |
| 			View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.httpcredentials, null);
 | |
| 		    if (!defaultPath.EndsWith(_schemeSeparator))
 | |
| 		    {
 | |
| 		        var webdavStorage = new Keepass2android.Javafilestorage.WebDavStorage(App.Kp2a.CertificateErrorHandler);
 | |
| 		        var connInfo = webdavStorage.SplitStringToConnectionInfo(defaultPath);
 | |
| 		        dlgContents.FindViewById<EditText>(Resource.Id.http_url).Text = connInfo.Url;
 | |
| 		        dlgContents.FindViewById<EditText>(Resource.Id.http_user).Text = connInfo.Username;
 | |
| 		        dlgContents.FindViewById<EditText>(Resource.Id.http_password).Text = connInfo.Password;
 | |
| 
 | |
| 
 | |
|             }
 | |
| 			builder.SetView(dlgContents);
 | |
| 			builder.SetPositiveButton(Android.Resource.String.Ok,
 | |
| 									  (sender, args) =>
 | |
| 									  {
 | |
| 										  string host = dlgContents.FindViewById<EditText>(Resource.Id.http_url).Text;
 | |
| 											
 | |
| 										  string user = dlgContents.FindViewById<EditText>(Resource.Id.http_user).Text;
 | |
| 										  string password = dlgContents.FindViewById<EditText>(Resource.Id.http_password).Text;
 | |
| 
 | |
| 										  string scheme = defaultPath.Substring(0, defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal));
 | |
| 										  if (host.Contains(_schemeSeparator) == false)
 | |
| 											  host = scheme + _schemeSeparator + host;
 | |
| 										  string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(host, user,
 | |
| 																										  password);
 | |
| 										  onStartBrowse(httpPath);
 | |
| 									  });
 | |
| 			EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>((sender, e) => onCancel());
 | |
| 
 | |
| 			builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
 | |
| 			builder.SetTitle(activity.GetString(Resource.String.enter_http_login_title));
 | |
| 			Dialog dialog = builder.Create();
 | |
| 
 | |
| 			dialog.Show();
 | |
| #endif
 | |
| 		}
 | |
| 
 | |
| 		private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
 | |
| 		{
 | |
| #if !NoNet
 | |
| 			MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
 | |
| 			View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.ftpcredentials, null);
 | |
| 		    if (!defaultPath.EndsWith(_schemeSeparator))
 | |
| 		    {
 | |
| 		        var connection = NetFtpFileStorage.ConnectionSettings.FromIoc(IOConnectionInfo.FromPath(defaultPath));
 | |
| 		        dlgContents.FindViewById<EditText>(Resource.Id.ftp_user).Text = connection.Username;
 | |
| 		        dlgContents.FindViewById<EditText>(Resource.Id.ftp_password).Text = connection.Password;
 | |
| 		        dlgContents.FindViewById<Spinner>(Resource.Id.ftp_encryption).SetSelection((int)connection.EncryptionMode);
 | |
| 
 | |
| 		        var uri = NetFtpFileStorage.IocToUri(IOConnectionInfo.FromPath(defaultPath));
 | |
|                 string pathAndQuery = uri.PathAndQuery;
 | |
| 
 | |
| 		        var host = uri.Host;
 | |
| 		        var localPath = WebUtility.UrlDecode(pathAndQuery);
 | |
| 
 | |
|                 
 | |
| 		        if (!uri.IsDefaultPort)
 | |
| 		        {
 | |
| 		            dlgContents.FindViewById<EditText>(Resource.Id.ftp_port).Text = uri.Port.ToString();
 | |
| 		        }
 | |
| 		        dlgContents.FindViewById<EditText>(Resource.Id.ftp_host).Text = host;
 | |
|                 dlgContents.FindViewById<EditText>(Resource.Id.ftp_initial_dir).Text = localPath;
 | |
| 
 | |
| 
 | |
|             }
 | |
|             builder.SetView(dlgContents);
 | |
| 			builder.SetPositiveButton(Android.Resource.String.Ok,
 | |
| 									  (sender, args) =>
 | |
| 									  {
 | |
| 										  string host = dlgContents.FindViewById<EditText>(Resource.Id.ftp_host).Text;
 | |
| 										  string portText = dlgContents.FindViewById<EditText>(Resource.Id.ftp_port).Text;
 | |
| 										  FtpEncryptionMode encryption =
 | |
| 											  (FtpEncryptionMode) dlgContents.FindViewById<Spinner>(Resource.Id.ftp_encryption).SelectedItemPosition;
 | |
| 										  int port = NetFtpFileStorage.GetDefaultPort(encryption);
 | |
| 										  if (!string.IsNullOrEmpty(portText))
 | |
| 											  int.TryParse(portText, out port);
 | |
| 										  string user = dlgContents.FindViewById<EditText>(Resource.Id.ftp_user).Text;
 | |
| 										  string password = dlgContents.FindViewById<EditText>(Resource.Id.ftp_password).Text;
 | |
| 										  string initialPath = dlgContents.FindViewById<EditText>(Resource.Id.ftp_initial_dir).Text;
 | |
| 										  string ftpPath = new NetFtpFileStorage(_activity, App.Kp2a, () => false)
 | |
| 											  .BuildFullPath(host, port, initialPath, user, password, encryption);
 | |
| 										  onStartBrowse(ftpPath);
 | |
| 									  });
 | |
| 			EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>((sender, e) => onCancel());
 | |
| 
 | |
| 			builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
 | |
| 			builder.SetTitle(activity.GetString(Resource.String.enter_ftp_login_title));
 | |
| 			Dialog dialog = builder.Create();
 | |
| 
 | |
| 			dialog.Show();
 | |
| #endif
 | |
| 		}
 | |
| 
 | |
| 
 | |
| 		private void ShowMegaDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
 | |
| 		{
 | |
| #if !NoNet
 | |
|             var settings = MegaFileStorage.GetAccountSettings(activity);
 | |
| 			MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
 | |
| 			View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.megacredentials, null);
 | |
| 			if (!defaultPath.EndsWith(_schemeSeparator))
 | |
|             {
 | |
|                 string user = "";
 | |
|                 string password = "";
 | |
|                 if (!defaultPath.EndsWith(_schemeSeparator))
 | |
| 				{
 | |
| 					 user = MegaFileStorage.GetAccount(defaultPath);
 | |
|                      if (!settings.PasswordByUsername.TryGetValue(user, out password))
 | |
|                          password = "";
 | |
|                      dlgContents.FindViewById<EditText>(Resource.Id.mega_user).Enabled = false;
 | |
| 
 | |
|                 }
 | |
| 				dlgContents.FindViewById<EditText>(Resource.Id.mega_user).Text = user;
 | |
| 				dlgContents.FindViewById<EditText>(Resource.Id.mega_password).Text = password;
 | |
| 
 | |
| 			}
 | |
| 
 | |
|             var userView = ((AutoCompleteTextView)dlgContents.FindViewById(Resource.Id.mega_user));
 | |
|             userView.Adapter = new ArrayAdapter(activity, Android.Resource.Layout.SimpleListItem1, Android.Resource.Id.Text1, settings.PasswordByUsername.Keys.ToArray());
 | |
| 
 | |
|             userView.TextChanged += (sender, args) =>
 | |
|             {
 | |
|                 if (userView.Text != null && settings.PasswordByUsername.TryGetValue(userView.Text, out string pwd))
 | |
|                 {
 | |
|                     dlgContents.FindViewById<EditText>(Resource.Id.mega_password).Text = pwd;
 | |
| 				}
 | |
|             };
 | |
|             builder.SetCancelable(false);
 | |
| 			builder.SetView(dlgContents);
 | |
| 			builder.SetPositiveButton(Android.Resource.String.Ok,
 | |
| 									  (sender, args) =>
 | |
| 									  {
 | |
| 										  string user = dlgContents.FindViewById<EditText>(Resource.Id.mega_user).Text;
 | |
| 										  string password = dlgContents.FindViewById<EditText>(Resource.Id.mega_password).Text;
 | |
| 										  //store the credentials in the mega credentials store:
 | |
|                                           
 | |
| 
 | |
|                                           settings.PasswordByUsername[user] = password;
 | |
| 
 | |
|                                           MegaFileStorage.UpdateAccountSettings(settings, activity);
 | |
| 
 | |
|                                           onStartBrowse(MegaFileStorage.ProtocolId + "://" + user);
 | |
|                                       });
 | |
| 			EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>((sender, e) => onCancel());
 | |
| 
 | |
| 			builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
 | |
| 			builder.SetTitle(activity.GetString(Resource.String.enter_mega_login_title));
 | |
| 			Dialog dialog = builder.Create();
 | |
| 
 | |
| 			dialog.Show();
 | |
| #endif
 | |
| 		}
 | |
| 
 | |
| 
 | |
| 
 | |
| 		public void PerformManualFileSelect(string defaultPath)
 | |
| 		{
 | |
| 			if (defaultPath.StartsWith("sftp://"))
 | |
| 				ShowSftpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath);
 | |
| 			else if ((defaultPath.StartsWith("ftp://")) || (defaultPath.StartsWith("ftps://")))
 | |
| 				ShowFtpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath);
 | |
| 			else if ((defaultPath.StartsWith("http://")) || (defaultPath.StartsWith("https://")))
 | |
| 				ShowHttpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath);
 | |
| 			else if (defaultPath.StartsWith("owncloud://"))
 | |
| 				ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "owncloud");
 | |
| 			else if (defaultPath.StartsWith("nextcloud://"))
 | |
| 			    ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "nextcloud");
 | |
|             else if (defaultPath.StartsWith("mega://"))
 | |
|                 ShowMegaDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath);
 | |
| 			else
 | |
| 			{
 | |
| 				Func<string, Dialog, bool> onOpen = OnOpenButton;
 | |
| 				Util.ShowFilenameDialog(_activity,
 | |
| 										!_isForSave ? onOpen : null,
 | |
| 										_isForSave ? onOpen : null,
 | |
| 										ReturnCancel,
 | |
| 										false,
 | |
| 										defaultPath,
 | |
| 										_activity.GetString(Resource.String.enter_filename_details_url),
 | |
| 										_requestCode)
 | |
| 				;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		private void ShowOwncloudDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath, string subtype)
 | |
| 		{
 | |
| #if !EXCLUDE_JAVAFILESTORAGE && !NoNet
 | |
| 			MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
 | |
| 			View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.owncloudcredentials, null);
 | |
| 			builder.SetView(dlgContents);
 | |
| 
 | |
| 			builder.SetPositiveButton(Android.Resource.String.Ok,
 | |
| 									  (sender, args) =>
 | |
| 									  {
 | |
| 										  string host = dlgContents.FindViewById<EditText>(Resource.Id.owncloud_url).Text;
 | |
| 
 | |
| 										  string user = dlgContents.FindViewById<EditText>(Resource.Id.http_user).Text;
 | |
| 										  string password = dlgContents.FindViewById<EditText>(Resource.Id.http_password).Text;
 | |
| 
 | |
| 										  string scheme = defaultPath.Substring(0,defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal));
 | |
| 										  if (host.Contains(_schemeSeparator) == false)
 | |
| 											  host = scheme + _schemeSeparator + host;
 | |
| 										  string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(WebDavFileStorage.Owncloud2Webdav(host, subtype == "owncloud" ? WebDavFileStorage.owncloudPrefix : WebDavFileStorage.nextcloudPrefix), user,
 | |
| 																										  password);
 | |
| 										  onStartBrowse(httpPath);
 | |
| 									  });
 | |
| 			EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>((sender, e) => onCancel());
 | |
| 
 | |
| 			builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
 | |
| 			builder.SetTitle(activity.GetString(subtype == "owncloud" ?  Resource.String.enter_owncloud_login_title : Resource.String.enter_nextcloud_login_title));
 | |
| 			Dialog dialog = builder.Create();
 | |
| 		    dlgContents.FindViewById<EditText>(Resource.Id.owncloud_url).SetHint(subtype == "owncloud" ? Resource.String.hint_owncloud_url : Resource.String.hint_nextcloud_url);
 | |
|             dialog.Show();
 | |
| #endif
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		private bool ReturnFileOrStartFileChooser(string filename)
 | |
| 		{
 | |
| 		    string filenameWithoutProt = filename;
 | |
| 		    if (filenameWithoutProt.Contains(_schemeSeparator))
 | |
| 		    {
 | |
| 		        filenameWithoutProt =
 | |
| 		            filenameWithoutProt.Substring(filenameWithoutProt.IndexOf(_schemeSeparator, StringComparison.Ordinal) + 3);
 | |
| 		    }
 | |
| 
 | |
|             int lastSlashPos = filenameWithoutProt.LastIndexOf('/');
 | |
| 			int lastDotPos = filenameWithoutProt.LastIndexOf('.');
 | |
| 			if ((lastSlashPos < 0 ) //no slash, probably only a server address (my.server.com)
 | |
|                 || (lastSlashPos >= lastDotPos)) //no dot after last slash or == in case neither / nor .
 | |
| 			{
 | |
| 				//looks like a folder.
 | |
| 				return StartFileChooser(filename);
 | |
| 			}
 | |
| 			//looks like a file
 | |
| 			IocSelected(null, IOConnectionInfo.FromPath(filename));
 | |
| 			return true;
 | |
| 		}
 | |
| 
 | |
| 		private void ReturnCancel()
 | |
| 		{
 | |
| 			if (OnCancel != null)
 | |
| 				OnCancel(this, null);
 | |
| 		}
 | |
| 
 | |
| 
 | |
| 		protected void ShowFilenameWarning(string fileName, Action onUserWantsToContinue, Action onUserWantsToCorrect)
 | |
| 		{
 | |
| 			new MaterialAlertDialogBuilder(_activity)
 | |
| 					.SetPositiveButton(Resource.String.Continue, delegate { onUserWantsToContinue(); })
 | |
| 					.SetMessage(Resource.String.NoFilenameWarning)
 | |
| 					.SetCancelable(false)
 | |
| 					.SetNegativeButton(Android.Resource.String.Cancel, delegate { onUserWantsToCorrect(); })
 | |
| 					.Create()
 | |
| 					.Show();
 | |
| 
 | |
| 		}
 | |
| 		private bool OnOpenButton(string filename, Dialog dialog)
 | |
| 		{
 | |
| 
 | |
| 			IOConnectionInfo ioc = new IOConnectionInfo
 | |
| 			{
 | |
| 				Path = filename
 | |
| 			};
 | |
| 
 | |
| 			// Make sure file name exists
 | |
| 			if (filename.Length == 0)
 | |
| 			{
 | |
| 				Toast.MakeText(_activity,
 | |
| 								Resource.String.error_filename_required,
 | |
| 								ToastLength.Long).Show();
 | |
| 				return false;
 | |
| 			}
 | |
| 
 | |
| 
 | |
| 			int lastSlashPos = filename.LastIndexOf('/');
 | |
| 			int lastDotPos = filename.LastIndexOf('.');
 | |
| 			if (lastSlashPos >= lastDotPos) //no dot after last slash or == in case neither / nor .
 | |
| 			{
 | |
| 				ShowFilenameWarning(filename, () => { IocSelected(null, ioc); dialog.Dismiss(); }, () => { /* don't do anything, leave dialog open, let user try again*/ });
 | |
| 				//signal that the dialog should be kept open
 | |
| 				return false;
 | |
| 			}
 | |
| 
 | |
| 			IFileStorage fileStorage;
 | |
| 			try
 | |
| 			{
 | |
| 				fileStorage = App.Kp2a.GetFileStorage(ioc);
 | |
| 			}
 | |
| 			catch (NoFileStorageFoundException)
 | |
| 			{
 | |
| 				Toast.MakeText(_activity,
 | |
| 								"Unexpected scheme in "+filename,
 | |
| 								ToastLength.Long).Show();
 | |
| 				return false;
 | |
| 			}
 | |
| 
 | |
| 			if (_isForSave && ioc.IsLocalFile())
 | |
| 			{
 | |
|                 // Try to create the file
 | |
|                 Java.IO.File file = new Java.IO.File(filename);
 | |
| 				try
 | |
| 				{
 | |
|                     Java.IO.File parent = file.ParentFile;
 | |
| 
 | |
| 					if (parent == null || (parent.Exists() && !parent.IsDirectory))
 | |
| 					{
 | |
| 						Toast.MakeText(_activity,
 | |
| 							            Resource.String.error_invalid_path,
 | |
| 							            ToastLength.Long).Show();
 | |
| 						return false;
 | |
| 					}
 | |
| 
 | |
| 					if (!parent.Exists())
 | |
| 					{
 | |
| 						// Create parent dircetory
 | |
| 						if (!parent.Mkdirs())
 | |
| 						{
 | |
| 							Toast.MakeText(_activity,
 | |
| 								            Resource.String.error_could_not_create_parent,
 | |
| 								            ToastLength.Long).Show();
 | |
| 							return false;
 | |
| 
 | |
| 						}
 | |
| 					}
 | |
| 					System.IO.File.Create(filename).Dispose();
 | |
| 
 | |
| 				}
 | |
| 				catch (Java.IO.IOException ex)
 | |
| 				{
 | |
| 					Toast.MakeText(
 | |
| 						_activity,
 | |
| 						_activity.GetText(Resource.String.error_file_not_create) + " "
 | |
| 						+ ex.LocalizedMessage,
 | |
| 						ToastLength.Long).Show();
 | |
| 					return false;
 | |
| 				}
 | |
| 
 | |
| 			}
 | |
| 			if (fileStorage.RequiresCredentials(ioc))
 | |
| 			{
 | |
| 				Util.QueryCredentials(ioc, iocResult => IocSelected(null, iocResult), _activity);
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				IocSelected(null, ioc);
 | |
| 			}
 | |
| 
 | |
| 			return true;
 | |
| 		}
 | |
| 
 | |
| 		private void IocSelected(Activity activity, IOConnectionInfo ioc)
 | |
| 		{
 | |
| 			if (OnOpen != null)
 | |
| 				OnOpen(activity, ioc);
 | |
| 		}
 | |
| 
 | |
| 		public bool StartFileChooser(string defaultPath)
 | |
| 		{
 | |
| #if !EXCLUDE_FILECHOOSER
 | |
| 			string fileProviderAuthority = FileChooserFileProvider.TheAuthority;
 | |
| 			if (defaultPath.StartsWith("file://"))
 | |
| 			{
 | |
| 				fileProviderAuthority = _activity.PackageName + ".android-filechooser.localfile";
 | |
| 			}
 | |
| 			Intent i = Keepass2android.Kp2afilechooser.Kp2aFileChooserBridge.GetLaunchFileChooserIntent(_activity, fileProviderAuthority,
 | |
| 																										defaultPath);
 | |
| 
 | |
| 
 | |
| 			if (_isForSave)
 | |
| 			{
 | |
| 				i.PutExtra("group.pals.android.lib.ui.filechooser.FileChooserActivity.save_dialog", true);
 | |
| 				string ext;
 | |
| 				if (!string.IsNullOrEmpty(DefaultExtension))
 | |
| 				{
 | |
| 					ext = DefaultExtension;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					ext = UrlUtil.GetExtension(defaultPath);
 | |
| 				}
 | |
| 				if ((ext != String.Empty) && (ext.Contains("?") == false))
 | |
| 					i.PutExtra("group.pals.android.lib.ui.filechooser.FileChooserActivity.default_file_ext", ext);
 | |
| 			}
 | |
| 			_activity.StartActivityForResult(i, _requestCode);
 | |
| 
 | |
| #else
 | |
| 			Toast.MakeText(LocaleManager.LocalizedAppContext, "File chooser is excluded!", ToastLength.Long).Show();
 | |
| #endif
 | |
| 			return true;
 | |
| 		}
 | |
| 
 | |
| 		public event EventHandler OnCancel;
 | |
| 
 | |
| 		public event EventHandler<IOConnectionInfo> OnOpen;
 | |
| 
 | |
| 	    public static bool CanEditIoc(IOConnectionInfo ioc)
 | |
| 	    {
 | |
| 	        return ioc.Path.StartsWith("http")
 | |
| 	               || ioc.Path.StartsWith("ftp")
 | |
| 	               || ioc.Path.StartsWith("sftp")
 | |
|                    || ioc.Path.StartsWith("mega");
 | |
| 
 | |
| 	    }
 | |
| 
 | |
| 	    public bool HandleActivityResult(Activity activity, int requestCode, Result resultCode, Intent data)
 | |
| 	    {
 | |
| 	        if (requestCode != _requestCode)
 | |
| 	            return false;
 | |
| 			
 | |
| 			if (resultCode == KeePass.ExitFileStorageSelectionOk)
 | |
| 			{
 | |
| 				string protocolId = data.GetStringExtra("protocolId");
 | |
| 				if (protocolId == "content")
 | |
| 				{
 | |
| 					Util.ShowBrowseDialog(activity, _requestCode, _isForSave, _tryGetPermanentAccess);
 | |
| 				}
 | |
| 				else
 | |
| 				{					
 | |
| 					App.Kp2a.GetFileStorage(protocolId).StartSelectFile(
 | |
| 							new FileStorageSetupInitiatorActivity(activity,(i, result, arg3) =>HandleActivityResult(activity, i, result, arg3),s => PerformManualFileSelect(s)), 
 | |
| 							_isForSave, 
 | |
| 							_requestCode, 
 | |
| 							protocolId);	
 | |
| 				}
 | |
| 			}
 | |
| 			
 | |
| 			if (resultCode == Result.Ok)
 | |
| 				{
 | |
| 					
 | |
| 					if (data.Data.Scheme == "content")
 | |
| 					{
 | |
| 						if ((int)Build.VERSION.SdkInt >= 19)
 | |
| 						{
 | |
| 							//try to take persistable permissions
 | |
| 							try
 | |
| 							{
 | |
| 								Kp2aLog.Log("TakePersistableUriPermission");
 | |
| 								var takeFlags = data.Flags
 | |
| 										& (ActivityFlags.GrantReadUriPermission
 | |
| 										| ActivityFlags.GrantWriteUriPermission);
 | |
| 								activity.ContentResolver.TakePersistableUriPermission(data.Data, takeFlags);
 | |
| 							}
 | |
| 							catch (Exception e)
 | |
| 							{
 | |
| 								Kp2aLog.Log(e.ToString());
 | |
| 							}
 | |
| 
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					
 | |
| 					string filename = Util.IntentToFilename(data, activity);
 | |
| 					if (filename == null)
 | |
| 						filename = data.DataString;
 | |
| 
 | |
| 					bool fileExists = data.GetBooleanExtra("group.pals.android.lib.ui.filechooser.FileChooserActivity.result_file_exists", true);
 | |
| 
 | |
| 					if (fileExists)
 | |
| 					{
 | |
| 						var ioc = new IOConnectionInfo { Path = ConvertFilenameToIocPath(filename) };
 | |
| 						IocSelected(activity,ioc);
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						var task = new CreateNewFilename(activity, new ActionOnFinish(activity, (success, messageOrFilename, newActivity) =>
 | |
| 							{
 | |
| 								if (!success)
 | |
| 								{
 | |
| 									Toast.MakeText(newActivity, messageOrFilename, ToastLength.Long).Show();
 | |
| 									return;
 | |
| 								}
 | |
| 								var ioc = new IOConnectionInfo { Path = ConvertFilenameToIocPath(messageOrFilename) };
 | |
| 							    IocSelected(newActivity, ioc);
 | |
| 								
 | |
| 							}), filename);
 | |
| 
 | |
| 						new ProgressTask(App.Kp2a, activity, task).Run();
 | |
| 					}
 | |
| 
 | |
| 				}
 | |
| 				
 | |
| 				
 | |
| 			if (resultCode == (Result)FileStorageResults.FileUsagePrepared)
 | |
| 			{
 | |
| 				var ioc = new IOConnectionInfo();
 | |
| 				Util.SetIoConnectionFromIntent(ioc, data);
 | |
| 				IocSelected(null, ioc);
 | |
| 			}
 | |
| 			if (resultCode == (Result)FileStorageResults.FileChooserPrepared )
 | |
| 			{
 | |
| 				IOConnectionInfo ioc = new IOConnectionInfo();
 | |
| 				Util.SetIoConnectionFromIntent(ioc, data);
 | |
| 				StartFileChooser(ioc.Path);
 | |
| 				
 | |
| 			}
 | |
| 	        return true;
 | |
| 
 | |
|         }
 | |
|     }
 | |
| } | 
