Compare commits
28 Commits
v1.09e-r3
...
v1.09e-r5b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
554f88c723 | ||
|
|
4cd32d30c6 | ||
|
|
d0da83182f | ||
|
|
ec5f26e0cd | ||
|
|
6110166af8 | ||
|
|
6f10a04589 | ||
|
|
e0c003fcb2 | ||
|
|
944f44bc4b | ||
|
|
58047d5386 | ||
|
|
c0a06c9f3a | ||
|
|
d0c041a0e2 | ||
|
|
df060e2f4b | ||
|
|
aea55dad45 | ||
|
|
5442dbf441 | ||
|
|
317476d9b5 | ||
|
|
ad0acb7a69 | ||
|
|
b66ae5d264 | ||
|
|
d87706fa43 | ||
|
|
cb25d12709 | ||
|
|
dce536009e | ||
|
|
656e785214 | ||
|
|
35d50a6eb0 | ||
|
|
786bb646c2 | ||
|
|
72cc6ff768 | ||
|
|
404e07e5c0 | ||
|
|
2378cd0d7c | ||
|
|
b149bab761 | ||
|
|
5ebd8e5e33 |
12
Makefile
12
Makefile
@@ -314,6 +314,18 @@ clean_KP2AKdbLibrary:
|
||||
cd src/java/KP2AKdbLibrary && $(GRADLEW) clean
|
||||
clean_PluginQR:
|
||||
cd src/java/PluginQR && $(GRADLEW) clean
|
||||
clean_rm:
|
||||
rm -rf src/*/obj
|
||||
rm -rf src/*/bin
|
||||
rm -rf src/java/*/app/build
|
||||
rm -rf src/java/argon2/obj
|
||||
rm -rf src/java/argon2/libs
|
||||
rm -rf src/packages
|
||||
rm -rf src/java/KP2AKdbLibrary/app/.cxx
|
||||
rm -rf src/java/KP2ASoftkeyboard_AS/app/.cxx
|
||||
rm -rf src/SamsungPass/Xamarin.SamsungPass/SamsungPass/bin
|
||||
rm -rf src/SamsungPass/Xamarin.SamsungPass/SamsungPass/obj
|
||||
|
||||
|
||||
# https://learn.microsoft.com/en-us/nuget/consume-packages/package-restore-troubleshooting#other-potential-conditions
|
||||
clean_nuget:
|
||||
|
||||
@@ -134,10 +134,10 @@ namespace Kp2aAutofillParser
|
||||
/// </summary>
|
||||
public class FilledAutofillFieldCollection<FieldT> where FieldT:InputField
|
||||
{
|
||||
public Dictionary<string, FilledAutofillField<FieldT>> HintMap { get; }
|
||||
public Dictionary<string, FilledAutofillField> HintMap { get; }
|
||||
public string DatasetName { get; set; }
|
||||
|
||||
public FilledAutofillFieldCollection(Dictionary<string, FilledAutofillField<FieldT>> hintMap, string datasetName = "")
|
||||
public FilledAutofillFieldCollection(Dictionary<string, FilledAutofillField> hintMap, string datasetName = "")
|
||||
{
|
||||
//recreate hint map making sure we compare case insensitive
|
||||
HintMap = BuildHintMap();
|
||||
@@ -149,9 +149,9 @@ namespace Kp2aAutofillParser
|
||||
public FilledAutofillFieldCollection() : this(BuildHintMap())
|
||||
{ }
|
||||
|
||||
private static Dictionary<string, FilledAutofillField<FieldT>> BuildHintMap()
|
||||
private static Dictionary<string, FilledAutofillField> BuildHintMap()
|
||||
{
|
||||
return new Dictionary<string, FilledAutofillField<FieldT>>(StringComparer.OrdinalIgnoreCase);
|
||||
return new Dictionary<string, FilledAutofillField>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -159,7 +159,7 @@ namespace Kp2aAutofillParser
|
||||
/// </summary>
|
||||
/// <returns>The add.</returns>
|
||||
/// <param name="filledAutofillField">Filled autofill field.</param>
|
||||
public void Add(FilledAutofillField<FieldT> filledAutofillField)
|
||||
public void Add(FilledAutofillField filledAutofillField)
|
||||
{
|
||||
foreach (string hint in filledAutofillField.AutofillHints)
|
||||
{
|
||||
@@ -572,7 +572,7 @@ namespace Kp2aAutofillParser
|
||||
}
|
||||
}
|
||||
|
||||
public class FilledAutofillField<FieldT> where FieldT : InputField
|
||||
public class FilledAutofillField
|
||||
{
|
||||
private string[] _autofillHints;
|
||||
public string TextValue { get; set; }
|
||||
@@ -611,13 +611,13 @@ namespace Kp2aAutofillParser
|
||||
public FilledAutofillField()
|
||||
{ }
|
||||
|
||||
public FilledAutofillField(FieldT inputField)
|
||||
public FilledAutofillField(InputField inputField)
|
||||
: this(inputField, inputField.AutofillHints)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public FilledAutofillField(FieldT inputField, string[] hints)
|
||||
public FilledAutofillField(InputField inputField, string[] hints)
|
||||
{
|
||||
|
||||
string[] rawHints = AutofillHintsHelper.FilterForSupportedHints(hints);
|
||||
@@ -665,6 +665,7 @@ namespace Kp2aAutofillParser
|
||||
}
|
||||
}
|
||||
AutofillHints = AutofillHintsHelper.ConvertToCanonicalLowerCaseHints(hintList.ToArray()).ToArray();
|
||||
inputField.FillFilledAutofillValue(this);
|
||||
|
||||
|
||||
}
|
||||
@@ -679,7 +680,7 @@ namespace Kp2aAutofillParser
|
||||
if (this == obj) return true;
|
||||
if (obj == null || GetType() != obj.GetType()) return false;
|
||||
|
||||
FilledAutofillField<FieldT> that = (FilledAutofillField<FieldT>)obj;
|
||||
FilledAutofillField that = (FilledAutofillField)obj;
|
||||
|
||||
if (!TextValue?.Equals(that.TextValue) ?? that.TextValue != null)
|
||||
return false;
|
||||
@@ -717,6 +718,8 @@ namespace Kp2aAutofillParser
|
||||
public string HtmlInfoTag { get; set; }
|
||||
public string HtmlInfoTypeAttribute { get; set; }
|
||||
|
||||
public abstract void FillFilledAutofillValue(FilledAutofillField filledField);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -838,7 +841,7 @@ namespace Kp2aAutofillParser
|
||||
continue;
|
||||
if (viewHints.Select(AutofillHintsHelper.ToCanonicalHint).Intersect(_autofillHintsForLogin).Any())
|
||||
{
|
||||
FieldsMappedToHints.Add(viewNode, viewHints.Select(AutofillHintsHelper.ToCanonicalHint).ToHashSet().ToArray());
|
||||
AddFieldToHintMap(viewNode, viewHints.Select(AutofillHintsHelper.ToCanonicalHint).ToHashSet().ToArray());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -873,9 +876,9 @@ namespace Kp2aAutofillParser
|
||||
if (passwordFields.Concat(usernameFields).Any(f => f.IsFocused))
|
||||
{
|
||||
foreach (var uf in usernameFields)
|
||||
FieldsMappedToHints.Add(uf, new string[] { AutofillHintsHelper.AutofillHintUsername });
|
||||
AddFieldToHintMap(uf, new string[] { AutofillHintsHelper.AutofillHintUsername });
|
||||
foreach (var pf in passwordFields.Except(usernameFields))
|
||||
FieldsMappedToHints.Add(pf, new string[] { AutofillHintsHelper.AutofillHintPassword });
|
||||
AddFieldToHintMap(pf, new string[] { AutofillHintsHelper.AutofillHintPassword });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -895,6 +898,18 @@ namespace Kp2aAutofillParser
|
||||
return result;
|
||||
}
|
||||
|
||||
private void AddFieldToHintMap(FieldT field, string[] hints)
|
||||
{
|
||||
if (FieldsMappedToHints.ContainsKey(field))
|
||||
{
|
||||
FieldsMappedToHints[field] = FieldsMappedToHints[field].Concat(hints).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
FieldsMappedToHints[field] = hints;
|
||||
}
|
||||
}
|
||||
|
||||
public bool LogAutofillView { get; set; }
|
||||
|
||||
private bool IsEditText(FieldT f)
|
||||
@@ -944,11 +959,6 @@ namespace Kp2aAutofillParser
|
||||
|
||||
private static bool IsPassword(InputField f)
|
||||
{
|
||||
if (f.IdEntry?.Contains("password") == true)
|
||||
{
|
||||
f = f;
|
||||
}
|
||||
|
||||
InputTypes inputType = f.InputType;
|
||||
|
||||
return
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace Kp2aAutofillParserTest
|
||||
class TestInputField: InputField
|
||||
{
|
||||
public string[] ExpectedAssignedHints { get; set; }
|
||||
public override void FillFilledAutofillValue(FilledAutofillField filledField)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.jcraft.jsch.UserInfo;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
public class SftpStorage extends JavaFileStorageBase {
|
||||
|
||||
@@ -323,6 +324,8 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
jsch = new JSch();
|
||||
ConnectionInfo ci = splitStringToConnectionInfo(filename);
|
||||
|
||||
Log.d("KP2AJFS", "init SFTP");
|
||||
|
||||
String base_dir = getBaseDir();
|
||||
jsch.setKnownHosts(base_dir + "/known_hosts");
|
||||
|
||||
@@ -340,7 +343,9 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
|
||||
}
|
||||
|
||||
Log.e("KP2AJFS[thread]", "getting session...");
|
||||
Session session = jsch.getSession(ci.username, ci.host, ci.port);
|
||||
Log.e("KP2AJFS", "creating SftpUserInfo");
|
||||
UserInfo ui = new SftpUserInfo(ci.password,_appContext);
|
||||
session.setUserInfo(ui);
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ public class SftpUserInfo implements UserInfo {
|
||||
builder.setContentText("SFTP prompt");
|
||||
builder.setSmallIcon(R.drawable.ic_logo_green_foreground);
|
||||
|
||||
|
||||
Handler h = new Handler() {
|
||||
public void handleMessage(Message M) {
|
||||
msg.copyFrom(M);
|
||||
@@ -51,8 +50,12 @@ public class SftpUserInfo implements UserInfo {
|
||||
intent.putExtra("keepass2android.sftp.prompt", text);
|
||||
intent.setData((Uri.parse("suckit://"+SystemClock.elapsedRealtime())));
|
||||
|
||||
|
||||
Log.e("KP2AJFS[thread]", "built after 2023-03-14");
|
||||
|
||||
int flags = 0;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
||||
Log.e("KP2AJFS[thread]", "Setting mutable flag...");
|
||||
flags |= PendingIntent.FLAG_MUTABLE;
|
||||
}
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(_appContext, 0, intent, flags);
|
||||
|
||||
@@ -530,7 +530,7 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
* parameters.
|
||||
*/
|
||||
private MatrixCursor doRetrieveFileInfo(Uri uri) {
|
||||
Log.d(CLASSNAME, "retrieve file info "+uri.toString());
|
||||
|
||||
MatrixCursor matrixCursor = BaseFileProviderUtils.newBaseFileCursor();
|
||||
|
||||
String filename = extractFile(uri);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="191"
|
||||
android:versionName="1.09e-r3"
|
||||
android:versionCode="194"
|
||||
android:versionName="1.09e-r5b"
|
||||
package="keepass2android.keepass2android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:installLocation="auto">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="191"
|
||||
android:versionName="1.09e-r3"
|
||||
android:versionCode="193"
|
||||
android:versionName="1.09e-r5"
|
||||
package="keepass2android.keepass2android_nonet"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:installLocation="auto">
|
||||
|
||||
@@ -689,6 +689,7 @@
|
||||
<item>Fejlrettelse til nedbrud og uventede log-outs</item>
|
||||
<item>Skift til ny SFTP-implementering, som understøtter moderne offentlige nøglealgoritmer såsom rsa-sha2-256</item>
|
||||
<item>Markér adgangskoder som følsomme ved kopiering til udklipsholder (Android 13)</item>
|
||||
<item>Autofill improvements</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09d">
|
||||
<item>Tilføjet understøttelse af visning, fjernelse og gendannelse af sikkerhedskopierede poster</item>
|
||||
|
||||
@@ -688,6 +688,7 @@ Anbei einige Tipps, die bei der Diagnose des Problems helfen können:\n
|
||||
<item>Fehlerbehebung, um Abstürze und unerwartetes Abmelden zu vermeiden</item>
|
||||
<item>Wechsel auf eine neue SFTP-Implementierung, jetzt mit Unterstützung von modernen Public-Key-Algorithmen wie rsa-sha2-256</item>
|
||||
<item>Passwörter werden beim Kopieren in die Zwischenablage als vertraulich markiert (Android 13)</item>
|
||||
<item>Autofill improvements</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09d">
|
||||
<item>Unterstützung für das Ansehen, Entfernen und Wiederherstellen von Eintragssicherungen hinzugefügt</item>
|
||||
|
||||
@@ -301,6 +301,8 @@
|
||||
<string name="NoDalVerification_summary">Απενεργοποιεί τον έλεγχο αν ταιριάζει ο τομέας και το πακέτο εφαρμογής</string>
|
||||
<string name="InlineSuggestions_title">Ενσωμάτωση με πληκτρολόγιο</string>
|
||||
<string name="InlineSuggestions_summary">Δείχνει τις προτάσεις αυτόματης συμπλήρωσης ως γραμμές μέσα στο πρηκτρολόγιο</string>
|
||||
<string name="LogAutofillView_title">Προβολή αυτόματης συμπλήρωσης αρχείου καταγραφής</string>
|
||||
<string name="LogAutofillView_summary">Εγγραφή λεπτομερειών σχετικά με την προβολή αυτόματης συμπλήρωσης στο αρχείο καταγραφής αποσφαλμάτωσης (αν η καταγραφή αποσφαλμάτωσης είναι ενεργοποιημένη). Αυτές οι λεπτομέρειες μπορούν να σταλούν στον προγραμματιστή αν η αυτόματη συμπλήρωση δε λειτουργεί όπως αναμενόταν.</string>
|
||||
<string name="requires_android11">Απαιτείται Android 11 ή νεότερη έκδοση</string>
|
||||
<string name="kp2a_findUrl">Εύρεση συνθηματικού</string>
|
||||
<string name="excludeExpiredEntries">Εξαίρεση ληγμένων εγγραφών</string>
|
||||
@@ -682,6 +684,12 @@
|
||||
<string name="EntryChannel_desc">Ειδοποίηση για απλοποιημένη πρόσβαση στην τρέχουσα καταχώριση.</string>
|
||||
<string name="CloseDbAfterFailedAttempts">Κλείσιμο της βάσης δεδομένων μετά από 3 ανεπιτυχείς προσπάθειες βιομετρικού ξεκλειδώματος.</string>
|
||||
<string name="WarnFingerprintInvalidated">Προσοχή! Ο βιομετρικός έλεγχος ταυτότητας μπορεί να ακυρωθεί από το Android, π.χ. μετά την προσθήκη ενός νέου δακτυλικού αποτυπώματος στις ρυθμίσεις της συσκευής σας. Βεβαιωθείτε ότι ξέρετε πάντα πώς να ξεκλειδώσετε με τον κύριο κωδικό πρόσβασης!</string>
|
||||
<string-array name="ChangeLog_1_09e">
|
||||
<item>Διόρθωση σφάλματος για απότομα κλεισίματα εφαρμογής και μη αναμενόμενες αποσυνδέσεις</item>
|
||||
<item>Μετάβαση σε νέα υλοποίηση SFTP, υποστηρίζοντας σύγχρονους αλγόριθμους δημόσιου κλειδιού όπως rsa-sha2-256</item>
|
||||
<item>Μαρκάρισμα κωδικών πρόσβασης ως ευαίσθητοι κατά την αντιγραφή στο πρόχειρο (Android 13)</item>
|
||||
<item>Autofill improvements</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09d">
|
||||
<item>Υποστηρίζεται πλέον προβολή, διαγραφή και ανάκτηση των διαγραμμένων εγγραφών</item>
|
||||
<item>Υλοποιήθηκε υποστήριξη για αποθήκευση στο νέφος MEGA </item>
|
||||
|
||||
@@ -687,6 +687,7 @@ Voici quelques conseils qui pourraient aider à diagnostiquer le problème : \n
|
||||
<item>Correction de bugs pour les plantages et les déconnexions inattendues</item>
|
||||
<item>Passage à une nouvelle implémentation SFTP, prenant en charge les algorithmes à clé publique modernes tels que rsa-sha2-256</item>
|
||||
<item>Marquer les mots de passe comme sensibles lors de la copie dans le presse-papiers (Android 13)</item>
|
||||
<item>Autofill improvements</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09d">
|
||||
<item>Ajout du support pour la visualisation, la suppression et la restauration des sauvegardes d\'entrée</item>
|
||||
|
||||
@@ -690,6 +690,7 @@
|
||||
<item>クラッシュと突発的にログアウトするバグを修正</item>
|
||||
<item>rsa-sha2-256 などの最新の公開鍵アルゴリズムをサポートする、新しい SFTP 実装に切り替え</item>
|
||||
<item>パスワードをクリップボードにコピーしたとき、機密コンテンツとしてマークするよう変更 (Android 13)</item>
|
||||
<item>Autofill improvements</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09d">
|
||||
<item>エントリーバックアップの表示、削除、および復元に対応</item>
|
||||
|
||||
@@ -690,6 +690,7 @@
|
||||
<item>Naprawa błędów awarii i nieoczekiwanych wylogowań</item>
|
||||
<item>Przełącz się na nową implementację SFTP, wspierając nowoczesne algorytmy klucza publicznego, takie jak rsa-sha2-256</item>
|
||||
<item>Oznacz hasła jako wrażliwe podczas kopiowania do schowka (Android 13)</item>
|
||||
<item>Autofill improvements</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09d">
|
||||
<item>Dodano wsparcie dla przeglądania, usuwania i przywracania kopii zapasowych wpisów</item>
|
||||
|
||||
@@ -303,6 +303,8 @@
|
||||
<string name="NoDalVerification_summary">Desativa a checagem se domínio e pacote do app correspondem.</string>
|
||||
<string name="InlineSuggestions_title">Integrar com o teclado</string>
|
||||
<string name="InlineSuggestions_summary">Mostra sugestões de Autopreenchimento como opção na linha no teclado (se suportado pelo método de entrada)</string>
|
||||
<string name="LogAutofillView_title">Registrar exibição de preenchimento automático</string>
|
||||
<string name="LogAutofillView_summary">Escreva detalhes sobre a exibição de preenchimento automático para registro de depuração (se o registro de depuração estiver habilitado). Esses detalhes podem ser enviados ao desenvolvedor se o preenchimento automático não funcionar conforme o esperado.</string>
|
||||
<string name="requires_android11">Requer Android 11 ou posterior</string>
|
||||
<string name="kp2a_findUrl">Keepass2Android: Buscar senha</string>
|
||||
<string name="excludeExpiredEntries">Excluir entradas expiradas</string>
|
||||
@@ -690,6 +692,7 @@
|
||||
<item>Correção de bugs em falhas e logouts inesperados</item>
|
||||
<item>Mudar para nova implementação SFTP, suportando algoritmos de chave pública modernos como rsa-sha2-256</item>
|
||||
<item>Marcar senhas como confidenciais ao copiar para a área de transferência (Android 13)</item>
|
||||
<item>Autofill improvements</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09d">
|
||||
<item>Adicionado suporte para visualização, remoção e restauração de backups de entrada</item>
|
||||
|
||||
@@ -690,6 +690,7 @@
|
||||
<item>Oprava chyby vedúcej k pádom a neočakávaným odhláseniam</item>
|
||||
<item>Prepnutie na novú implementáciu SFTP, s podporou pre moderné algoritmy verejných kľúčov, ako je rsa-sha2-256</item>
|
||||
<item>Označiť heslá ako citlivé údaje pri kopírovaní do schránky (Android 13)</item>
|
||||
<item>Autofill improvements</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09d">
|
||||
<item>Pridaná podpora prehliadania, odstraňovania a obnovovania záloh záznamu</item>
|
||||
|
||||
@@ -690,6 +690,7 @@
|
||||
<item>Popravek napak pri zrušitvah in nepričakovanih odjavah</item>
|
||||
<item>Preklopite na novo izvedbo SFTP, ki podpira sodobne algoritme javnih ključev, kot je rsa-sha2-256</item>
|
||||
<item>Označi gesla kot občutljiva pri kopiranju v odložišče (Android 13)</item>
|
||||
<item>Autofill improvements</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09d">
|
||||
<item>Dodana podpora za ogled, odstranjevanje in obnavljanje varnostnih kopij vnosov</item>
|
||||
|
||||
@@ -690,6 +690,7 @@
|
||||
<item>Виправлено помилку зі збоями та несподіваними виходами</item>
|
||||
<item>Перехід на нову реалізацію SFTP з підтримкою сучасних алгоритмів публічного ключа, як-от rsa-sha2-256</item>
|
||||
<item>Позначати паролі як вразливі під час копіювання до буфера обміну (Android 13)</item>
|
||||
<item>Autofill improvements</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09d">
|
||||
<item>Додано підтримку перегляду, видалення та відновлення резервних копій записів</item>
|
||||
|
||||
@@ -690,6 +690,7 @@
|
||||
<item>修正當機和意外登出的錯誤</item>
|
||||
<item>切換成新 SFTP 實作,支援現代公鑰演算法,如 rsa-sha2-256</item>
|
||||
<item>複製密碼到剪貼簿時設為機密 (Android 13)</item>
|
||||
<item>Autofill improvements</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09d">
|
||||
<item>支援查看、移除和還原項目備份</item>
|
||||
|
||||
@@ -301,6 +301,8 @@
|
||||
<string name="NoDalVerification_summary">不检查域名和应用程序包是否匹配</string>
|
||||
<string name="InlineSuggestions_title">与键盘集成</string>
|
||||
<string name="InlineSuggestions_summary">将自动填充建议显示为键盘中的内联选项(如果输入法支持的话)</string>
|
||||
<string name="LogAutofillView_title">记录自动填充视图</string>
|
||||
<string name="LogAutofillView_summary">将自动填充视图的详情写入调试日志 (如果启用了调试日志)。 如果自动填充无法正常工作,这些细节可以发送给开发者。</string>
|
||||
<string name="requires_android11">需要 Android 11 或更高版本</string>
|
||||
<string name="kp2a_findUrl">找回密码</string>
|
||||
<string name="excludeExpiredEntries">排除过期的条目</string>
|
||||
@@ -689,6 +691,7 @@
|
||||
<item>修复崩溃和意外注销的 bug</item>
|
||||
<item>切换到新的 SFTP 实现,支持现代公钥算法,例如 rsa-sha2-256</item>
|
||||
<item>复制到剪贴板时将密码标记为敏感 (Android 13)</item>
|
||||
<item>Autofill improvements</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09d">
|
||||
<item>新增查看、移除和恢复条目备份的功能</item>
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace keepass2android.services.AutofillBase
|
||||
{
|
||||
foreach (AutofillFieldMetadata autofillFieldMetadata in autofillFieldMetadataCollection.GetFieldsForHint(hint))
|
||||
{
|
||||
FilledAutofillField<ViewNodeInputField> filledAutofillField;
|
||||
FilledAutofillField filledAutofillField;
|
||||
if (!filledAutofillFieldCollection.HintMap.TryGetValue(hint, out filledAutofillField) || (filledAutofillField == null))
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace keepass2android.services.AutofillBase
|
||||
[JsonIgnore]
|
||||
public AssistStructure.ViewNode ViewNode { get; set; }
|
||||
|
||||
public void FillFilledAutofillValue(FilledAutofillField<ViewNodeInputField> filledField)
|
||||
public override void FillFilledAutofillValue(FilledAutofillField filledField)
|
||||
{
|
||||
AutofillValue autofillValue = ViewNode.AutofillValue;
|
||||
if (autofillValue != null)
|
||||
@@ -159,8 +159,12 @@ namespace keepass2android.services.AutofillBase
|
||||
|
||||
protected override AutofillTargetId Parse(bool forFill, bool isManualRequest, AutofillView<ViewNodeInputField> autofillView)
|
||||
{
|
||||
if (autofillView == null)
|
||||
Kp2aLog.Log("Received null autofill view!");
|
||||
var result = base.Parse(forFill, isManualRequest, autofillView);
|
||||
|
||||
Kp2aLog.Log("Parsing done");
|
||||
|
||||
if (forFill)
|
||||
{
|
||||
foreach (var p in FieldsMappedToHints)
|
||||
@@ -168,8 +172,9 @@ namespace keepass2android.services.AutofillBase
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientFormData = new FilledAutofillFieldCollection<ViewNodeInputField>();
|
||||
foreach (var p in FieldsMappedToHints)
|
||||
ClientFormData.Add(new FilledAutofillField<ViewNodeInputField>(p.Key, p.Value));
|
||||
ClientFormData.Add(new FilledAutofillField(p.Key, p.Value));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -61,8 +61,8 @@ namespace keepass2android.services.Kp2aAutofill
|
||||
foreach (string key in pwEntryOutput.OutputStrings.GetKeys())
|
||||
{
|
||||
|
||||
FilledAutofillField<ViewNodeInputField> field =
|
||||
new FilledAutofillField<ViewNodeInputField>
|
||||
FilledAutofillField field =
|
||||
new FilledAutofillField
|
||||
{
|
||||
AutofillHints = GetCanonicalHintsFromKp2aField(key).ToArray(),
|
||||
TextValue = pwEntryOutput.OutputStrings.ReadSafe(key)
|
||||
@@ -73,8 +73,8 @@ namespace keepass2android.services.Kp2aAutofill
|
||||
if (IsCreditCard(pwEntry, context) && pwEntry.Expires)
|
||||
{
|
||||
DateTime expTime = pwEntry.ExpiryTime;
|
||||
FilledAutofillField<ViewNodeInputField> field =
|
||||
new FilledAutofillField<ViewNodeInputField>
|
||||
FilledAutofillField field =
|
||||
new FilledAutofillField
|
||||
{
|
||||
AutofillHints = new[] {View.AutofillHintCreditCardExpirationDate},
|
||||
DateValue = (long) (1000 * TimeUtil.SerializeUnix(expTime))
|
||||
@@ -82,7 +82,7 @@ namespace keepass2android.services.Kp2aAutofill
|
||||
fieldCollection.Add(field);
|
||||
|
||||
field =
|
||||
new FilledAutofillField<ViewNodeInputField>
|
||||
new FilledAutofillField
|
||||
{
|
||||
AutofillHints = new[] {View.AutofillHintCreditCardExpirationDay},
|
||||
TextValue = expTime.Day.ToString()
|
||||
@@ -90,7 +90,7 @@ namespace keepass2android.services.Kp2aAutofill
|
||||
fieldCollection.Add(field);
|
||||
|
||||
field =
|
||||
new FilledAutofillField<ViewNodeInputField>
|
||||
new FilledAutofillField
|
||||
{
|
||||
AutofillHints = new[] {View.AutofillHintCreditCardExpirationMonth},
|
||||
TextValue = expTime.Month.ToString()
|
||||
@@ -98,7 +98,7 @@ namespace keepass2android.services.Kp2aAutofill
|
||||
fieldCollection.Add(field);
|
||||
|
||||
field =
|
||||
new FilledAutofillField<ViewNodeInputField>
|
||||
new FilledAutofillField
|
||||
{
|
||||
AutofillHints = new[] {View.AutofillHintCreditCardExpirationYear},
|
||||
TextValue = expTime.Year.ToString()
|
||||
|
||||
@@ -1,956 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Formatting = System.Xml.Formatting;
|
||||
|
||||
namespace Kp2aAutofillParser
|
||||
{
|
||||
public class W3cHints
|
||||
{
|
||||
|
||||
// Supported W3C autofill tokens (https://html.spec.whatwg.org/multipage/forms.html#autofill)
|
||||
public const string HONORIFIC_PREFIX = "honorific-prefix";
|
||||
public const string NAME = "name";
|
||||
public const string GIVEN_NAME = "given-name";
|
||||
public const string ADDITIONAL_NAME = "additional-name";
|
||||
public const string FAMILY_NAME = "family-name";
|
||||
public const string HONORIFIC_SUFFIX = "honorific-suffix";
|
||||
public const string USERNAME = "username";
|
||||
public const string NEW_PASSWORD = "new-password";
|
||||
public const string CURRENT_PASSWORD = "current-password";
|
||||
public const string ORGANIZATION_TITLE = "organization-title";
|
||||
public const string ORGANIZATION = "organization";
|
||||
public const string STREET_ADDRESS = "street-address";
|
||||
public const string ADDRESS_LINE1 = "address-line1";
|
||||
public const string ADDRESS_LINE2 = "address-line2";
|
||||
public const string ADDRESS_LINE3 = "address-line3";
|
||||
public const string ADDRESS_LEVEL4 = "address-level4";
|
||||
public const string ADDRESS_LEVEL3 = "address-level3";
|
||||
public const string ADDRESS_LEVEL2 = "address-level2";
|
||||
public const string ADDRESS_LEVEL1 = "address-level1";
|
||||
public const string COUNTRY = "country";
|
||||
public const string COUNTRY_NAME = "country-name";
|
||||
public const string POSTAL_CODE = "postal-code";
|
||||
public const string CC_NAME = "cc-name";
|
||||
public const string CC_GIVEN_NAME = "cc-given-name";
|
||||
public const string CC_ADDITIONAL_NAME = "cc-additional-name";
|
||||
public const string CC_FAMILY_NAME = "cc-family-name";
|
||||
public const string CC_NUMBER = "cc-number";
|
||||
public const string CC_EXPIRATION = "cc-exp";
|
||||
public const string CC_EXPIRATION_MONTH = "cc-exp-month";
|
||||
public const string CC_EXPIRATION_YEAR = "cc-exp-year";
|
||||
public const string CC_CSC = "cc-csc";
|
||||
public const string CC_TYPE = "cc-type";
|
||||
public const string TRANSACTION_CURRENCY = "transaction-currency";
|
||||
public const string TRANSACTION_AMOUNT = "transaction-amount";
|
||||
public const string LANGUAGE = "language";
|
||||
public const string BDAY = "bday";
|
||||
public const string BDAY_DAY = "bday-day";
|
||||
public const string BDAY_MONTH = "bday-month";
|
||||
public const string BDAY_YEAR = "bday-year";
|
||||
public const string SEX = "sex";
|
||||
public const string URL = "url";
|
||||
public const string PHOTO = "photo";
|
||||
// Optional W3C prefixes
|
||||
public const string PREFIX_SECTION = "section-";
|
||||
public const string SHIPPING = "shipping";
|
||||
public const string BILLING = "billing";
|
||||
// W3C prefixes below...
|
||||
public const string PREFIX_HOME = "home";
|
||||
public const string PREFIX_WORK = "work";
|
||||
public const string PREFIX_FAX = "fax";
|
||||
public const string PREFIX_PAGER = "pager";
|
||||
// ... require those suffix
|
||||
public const string TEL = "tel";
|
||||
public const string TEL_COUNTRY_CODE = "tel-country-code";
|
||||
public const string TEL_NATIONAL = "tel-national";
|
||||
public const string TEL_AREA_CODE = "tel-area-code";
|
||||
public const string TEL_LOCAL = "tel-local";
|
||||
public const string TEL_LOCAL_PREFIX = "tel-local-prefix";
|
||||
public const string TEL_LOCAL_SUFFIX = "tel-local-suffix";
|
||||
public const string TEL_EXTENSION = "tel_extension";
|
||||
public const string EMAIL = "email";
|
||||
public const string IMPP = "impp";
|
||||
|
||||
private W3cHints()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static bool isW3cSectionPrefix(string hint)
|
||||
{
|
||||
return hint.ToLower().StartsWith(W3cHints.PREFIX_SECTION);
|
||||
}
|
||||
|
||||
public static bool isW3cAddressType(string hint)
|
||||
{
|
||||
switch (hint.ToLower())
|
||||
{
|
||||
case W3cHints.SHIPPING:
|
||||
case W3cHints.BILLING:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool isW3cTypePrefix(string hint)
|
||||
{
|
||||
switch (hint.ToLower())
|
||||
{
|
||||
case W3cHints.PREFIX_WORK:
|
||||
case W3cHints.PREFIX_FAX:
|
||||
case W3cHints.PREFIX_HOME:
|
||||
case W3cHints.PREFIX_PAGER:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool isW3cTypeHint(string hint)
|
||||
{
|
||||
switch (hint.ToLower())
|
||||
{
|
||||
case W3cHints.TEL:
|
||||
case W3cHints.TEL_COUNTRY_CODE:
|
||||
case W3cHints.TEL_NATIONAL:
|
||||
case W3cHints.TEL_AREA_CODE:
|
||||
case W3cHints.TEL_LOCAL:
|
||||
case W3cHints.TEL_LOCAL_PREFIX:
|
||||
case W3cHints.TEL_LOCAL_SUFFIX:
|
||||
case W3cHints.TEL_EXTENSION:
|
||||
case W3cHints.EMAIL:
|
||||
case W3cHints.IMPP:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// FilledAutofillFieldCollection is the model that holds all of the data on a client app's page,
|
||||
/// plus the dataset name associated with it.
|
||||
/// </summary>
|
||||
public class FilledAutofillFieldCollection<FieldT> where FieldT:InputField
|
||||
{
|
||||
public Dictionary<string, FilledAutofillField<FieldT>> HintMap { get; }
|
||||
public string DatasetName { get; set; }
|
||||
|
||||
public FilledAutofillFieldCollection(Dictionary<string, FilledAutofillField<FieldT>> hintMap, string datasetName = "")
|
||||
{
|
||||
//recreate hint map making sure we compare case insensitive
|
||||
HintMap = BuildHintMap();
|
||||
foreach (var p in hintMap)
|
||||
HintMap.Add(p.Key, p.Value);
|
||||
DatasetName = datasetName;
|
||||
}
|
||||
|
||||
public FilledAutofillFieldCollection() : this(BuildHintMap())
|
||||
{ }
|
||||
|
||||
private static Dictionary<string, FilledAutofillField<FieldT>> BuildHintMap()
|
||||
{
|
||||
return new Dictionary<string, FilledAutofillField<FieldT>>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a filledAutofillField to the collection, indexed by all of its hints.
|
||||
/// </summary>
|
||||
/// <returns>The add.</returns>
|
||||
/// <param name="filledAutofillField">Filled autofill field.</param>
|
||||
public void Add(FilledAutofillField<FieldT> filledAutofillField)
|
||||
{
|
||||
foreach (string hint in filledAutofillField.AutofillHints)
|
||||
{
|
||||
if (AutofillHintsHelper.IsSupportedHint(hint))
|
||||
{
|
||||
HintMap.TryAdd(hint, filledAutofillField);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Takes in a list of autofill hints (`autofillHints`), usually associated with a View or set of
|
||||
/// Views. Returns whether any of the filled fields on the page have at least 1 of these
|
||||
/// `autofillHint`s.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c>, if with hints was helpsed, <c>false</c> otherwise.</returns>
|
||||
/// <param name="autofillHints">Autofill hints.</param>
|
||||
public bool HelpsWithHints(List<string> autofillHints)
|
||||
{
|
||||
for (int i = 0; i < autofillHints.Count; i++)
|
||||
{
|
||||
var autofillHint = autofillHints[i];
|
||||
if (HintMap.ContainsKey(autofillHint) && !HintMap[autofillHint].IsNull())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public class AutofillHintsHelper
|
||||
{
|
||||
public const string AutofillHint2faAppOtp = "2faAppOTPCode";
|
||||
public const string AutofillHintBirthDateDay = "birthDateDay";
|
||||
public const string AutofillHintBirthDateFull = "birthDateFull";
|
||||
public const string AutofillHintBirthDateMonth = "birthDateMonth";
|
||||
public const string AutofillHintBirthDateYear = "birthDateYear";
|
||||
public const string AutofillHintCreditCardExpirationDate = "creditCardExpirationDate";
|
||||
public const string AutofillHintCreditCardExpirationDay = "creditCardExpirationDay";
|
||||
public const string AutofillHintCreditCardExpirationMonth = "creditCardExpirationMonth";
|
||||
public const string AutofillHintCreditCardExpirationYear = "creditCardExpirationYear";
|
||||
public const string AutofillHintCreditCardNumber = "creditCardNumber";
|
||||
public const string AutofillHintCreditCardSecurityCode = "creditCardSecurityCode";
|
||||
public const string AutofillHintEmailAddress = "emailAddress";
|
||||
public const string AutofillHintEmailOtp = "emailOTPCode";
|
||||
public const string AutofillHintGender = "gender";
|
||||
public const string AutofillHintName = "name";
|
||||
public const string AutofillHintNewPassword = "newPassword";
|
||||
public const string AutofillHintNewUsername = "newUsername";
|
||||
public const string AutofillHintNotApplicable = "notApplicable";
|
||||
public const string AutofillHintPassword = "password";
|
||||
public const string AutofillHintPersonName = "personName";
|
||||
public const string AutofillHintPersonNameFAMILY = "personFamilyName";
|
||||
public const string AutofillHintPersonNameGIVEN = "personGivenName";
|
||||
public const string AutofillHintPersonNameMIDDLE = "personMiddleName";
|
||||
public const string AutofillHintPersonNameMIDDLE_INITIAL = "personMiddleInitial";
|
||||
public const string AutofillHintPersonNamePREFIX = "personNamePrefix";
|
||||
public const string AutofillHintPersonNameSUFFIX = "personNameSuffix";
|
||||
public const string AutofillHintPhone = "phone";
|
||||
public const string AutofillHintPhoneContryCode = "phoneCountryCode";
|
||||
public const string AutofillHintPostalAddressAPT_NUMBER = "aptNumber";
|
||||
public const string AutofillHintPostalAddressCOUNTRY = "addressCountry";
|
||||
public const string AutofillHintPostalAddressDEPENDENT_LOCALITY = "dependentLocality";
|
||||
public const string AutofillHintPostalAddressEXTENDED_ADDRESS = "extendedAddress";
|
||||
public const string AutofillHintPostalAddressEXTENDED_POSTAL_CODE = "extendedPostalCode";
|
||||
public const string AutofillHintPostalAddressLOCALITY = "addressLocality";
|
||||
public const string AutofillHintPostalAddressREGION = "addressRegion";
|
||||
public const string AutofillHintPostalAddressSTREET_ADDRESS = "streetAddress";
|
||||
public const string AutofillHintPostalCode = "postalCode";
|
||||
public const string AutofillHintPromoCode = "promoCode";
|
||||
public const string AutofillHintSMS_OTP = "smsOTPCode";
|
||||
public const string AutofillHintUPI_VPA = "upiVirtualPaymentAddress";
|
||||
public const string AutofillHintUsername = "username";
|
||||
public const string AutofillHintWifiPassword = "wifiPassword";
|
||||
public const string AutofillHintPhoneNational = "phoneNational";
|
||||
public const string AutofillHintPhoneNumber = "phoneNumber";
|
||||
public const string AutofillHintPhoneNumberDevice = "phoneNumberDevice";
|
||||
public const string AutofillHintPostalAddress = "postalAddress";
|
||||
|
||||
private static readonly HashSet<string> _allSupportedHints = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
AutofillHintCreditCardExpirationDate,
|
||||
AutofillHintCreditCardExpirationDay,
|
||||
AutofillHintCreditCardExpirationMonth,
|
||||
AutofillHintCreditCardExpirationYear,
|
||||
AutofillHintCreditCardNumber,
|
||||
AutofillHintCreditCardSecurityCode,
|
||||
AutofillHintEmailAddress,
|
||||
AutofillHintPhone,
|
||||
AutofillHintName,
|
||||
AutofillHintPassword,
|
||||
AutofillHintPostalAddress,
|
||||
AutofillHintPostalCode,
|
||||
AutofillHintUsername,
|
||||
W3cHints.HONORIFIC_PREFIX,
|
||||
W3cHints.NAME,
|
||||
W3cHints.GIVEN_NAME,
|
||||
W3cHints.ADDITIONAL_NAME,
|
||||
W3cHints.FAMILY_NAME,
|
||||
W3cHints.HONORIFIC_SUFFIX,
|
||||
W3cHints.USERNAME,
|
||||
W3cHints.NEW_PASSWORD,
|
||||
W3cHints.CURRENT_PASSWORD,
|
||||
W3cHints.ORGANIZATION_TITLE,
|
||||
W3cHints.ORGANIZATION,
|
||||
W3cHints.STREET_ADDRESS,
|
||||
W3cHints.ADDRESS_LINE1,
|
||||
W3cHints.ADDRESS_LINE2,
|
||||
W3cHints.ADDRESS_LINE3,
|
||||
W3cHints.ADDRESS_LEVEL4,
|
||||
W3cHints.ADDRESS_LEVEL3,
|
||||
W3cHints.ADDRESS_LEVEL2,
|
||||
W3cHints.ADDRESS_LEVEL1,
|
||||
W3cHints.COUNTRY,
|
||||
W3cHints.COUNTRY_NAME,
|
||||
W3cHints.POSTAL_CODE,
|
||||
W3cHints.CC_NAME,
|
||||
W3cHints.CC_GIVEN_NAME,
|
||||
W3cHints.CC_ADDITIONAL_NAME,
|
||||
W3cHints.CC_FAMILY_NAME,
|
||||
W3cHints.CC_NUMBER,
|
||||
W3cHints.CC_EXPIRATION,
|
||||
W3cHints.CC_EXPIRATION_MONTH,
|
||||
W3cHints.CC_EXPIRATION_YEAR,
|
||||
W3cHints.CC_CSC,
|
||||
W3cHints.CC_TYPE,
|
||||
W3cHints.TRANSACTION_CURRENCY,
|
||||
W3cHints.TRANSACTION_AMOUNT,
|
||||
W3cHints.LANGUAGE,
|
||||
W3cHints.BDAY,
|
||||
W3cHints.BDAY_DAY,
|
||||
W3cHints.BDAY_MONTH,
|
||||
W3cHints.BDAY_YEAR,
|
||||
W3cHints.SEX,
|
||||
W3cHints.URL,
|
||||
W3cHints.PHOTO,
|
||||
W3cHints.TEL,
|
||||
W3cHints.TEL_COUNTRY_CODE,
|
||||
W3cHints.TEL_NATIONAL,
|
||||
W3cHints.TEL_AREA_CODE,
|
||||
W3cHints.TEL_LOCAL,
|
||||
W3cHints.TEL_LOCAL_PREFIX,
|
||||
W3cHints.TEL_LOCAL_SUFFIX,
|
||||
W3cHints.TEL_EXTENSION,
|
||||
W3cHints.EMAIL,
|
||||
W3cHints.IMPP,
|
||||
};
|
||||
|
||||
private static readonly List<HashSet<string>> partitionsOfCanonicalHints = new List<HashSet<string>>()
|
||||
{
|
||||
|
||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
AutofillHintEmailAddress,
|
||||
AutofillHintPhone,
|
||||
AutofillHintName,
|
||||
AutofillHintPassword,
|
||||
AutofillHintUsername,
|
||||
W3cHints.HONORIFIC_PREFIX,
|
||||
W3cHints.NAME,
|
||||
W3cHints.GIVEN_NAME,
|
||||
W3cHints.ADDITIONAL_NAME,
|
||||
W3cHints.FAMILY_NAME,
|
||||
W3cHints.HONORIFIC_SUFFIX,
|
||||
W3cHints.ORGANIZATION_TITLE,
|
||||
W3cHints.ORGANIZATION,
|
||||
W3cHints.LANGUAGE,
|
||||
W3cHints.BDAY,
|
||||
W3cHints.BDAY_DAY,
|
||||
W3cHints.BDAY_MONTH,
|
||||
W3cHints.BDAY_YEAR,
|
||||
W3cHints.SEX,
|
||||
W3cHints.URL,
|
||||
W3cHints.PHOTO,
|
||||
W3cHints.TEL,
|
||||
W3cHints.TEL_COUNTRY_CODE,
|
||||
W3cHints.TEL_NATIONAL,
|
||||
W3cHints.TEL_AREA_CODE,
|
||||
W3cHints.TEL_LOCAL,
|
||||
W3cHints.TEL_LOCAL_PREFIX,
|
||||
W3cHints.TEL_LOCAL_SUFFIX,
|
||||
W3cHints.TEL_EXTENSION,
|
||||
W3cHints.IMPP,
|
||||
},
|
||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
AutofillHintPostalAddress,
|
||||
AutofillHintPostalCode,
|
||||
|
||||
W3cHints.STREET_ADDRESS,
|
||||
W3cHints.ADDRESS_LINE1,
|
||||
W3cHints.ADDRESS_LINE2,
|
||||
W3cHints.ADDRESS_LINE3,
|
||||
W3cHints.ADDRESS_LEVEL4,
|
||||
W3cHints.ADDRESS_LEVEL3,
|
||||
W3cHints.ADDRESS_LEVEL2,
|
||||
W3cHints.ADDRESS_LEVEL1,
|
||||
W3cHints.COUNTRY,
|
||||
W3cHints.COUNTRY_NAME
|
||||
},
|
||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
AutofillHintCreditCardExpirationDate,
|
||||
AutofillHintCreditCardExpirationDay,
|
||||
AutofillHintCreditCardExpirationMonth,
|
||||
AutofillHintCreditCardExpirationYear,
|
||||
AutofillHintCreditCardNumber,
|
||||
AutofillHintCreditCardSecurityCode,
|
||||
|
||||
W3cHints.CC_NAME,
|
||||
W3cHints.CC_GIVEN_NAME,
|
||||
W3cHints.CC_ADDITIONAL_NAME,
|
||||
W3cHints.CC_FAMILY_NAME,
|
||||
W3cHints.CC_TYPE,
|
||||
W3cHints.TRANSACTION_CURRENCY,
|
||||
W3cHints.TRANSACTION_AMOUNT,
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> hintToCanonicalReplacement = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{W3cHints.EMAIL, AutofillHintEmailAddress},
|
||||
{W3cHints.USERNAME, AutofillHintUsername},
|
||||
{W3cHints.CURRENT_PASSWORD, AutofillHintPassword},
|
||||
{W3cHints.NEW_PASSWORD, AutofillHintPassword},
|
||||
{W3cHints.CC_EXPIRATION_MONTH, AutofillHintCreditCardExpirationMonth },
|
||||
{W3cHints.CC_EXPIRATION_YEAR, AutofillHintCreditCardExpirationYear },
|
||||
{W3cHints.CC_EXPIRATION, AutofillHintCreditCardExpirationDate },
|
||||
{W3cHints.CC_NUMBER, AutofillHintCreditCardNumber },
|
||||
{W3cHints.CC_CSC, AutofillHintCreditCardSecurityCode },
|
||||
{W3cHints.POSTAL_CODE, AutofillHintPostalCode },
|
||||
|
||||
|
||||
};
|
||||
|
||||
public static bool IsSupportedHint(string hint)
|
||||
{
|
||||
return _allSupportedHints.Contains(hint);
|
||||
}
|
||||
|
||||
|
||||
public static string[] FilterForSupportedHints(string[] hints)
|
||||
{
|
||||
if (hints == null)
|
||||
return Array.Empty<string>();
|
||||
var filteredHints = new string[hints.Length];
|
||||
int i = 0;
|
||||
foreach (var hint in hints)
|
||||
{
|
||||
if (IsSupportedHint(hint))
|
||||
{
|
||||
filteredHints[i++] = hint;
|
||||
}
|
||||
|
||||
}
|
||||
var finalFilteredHints = new string[i];
|
||||
Array.Copy(filteredHints, 0, finalFilteredHints, 0, i);
|
||||
return finalFilteredHints;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// transforms hints by replacing some W3cHints by their Android counterparts and transforming everything to lowercase
|
||||
/// </summary>
|
||||
public static List<string> ConvertToCanonicalHints(string[] supportedHints)
|
||||
{
|
||||
List<string> result = new List<string>();
|
||||
foreach (string hint in supportedHints)
|
||||
{
|
||||
string canonicalHint;
|
||||
if (!hintToCanonicalReplacement.TryGetValue(hint, out canonicalHint))
|
||||
canonicalHint = hint;
|
||||
result.Add(canonicalHint.ToLower());
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
public static int GetPartitionIndex(string hint)
|
||||
{
|
||||
for (int i = 0; i < partitionsOfCanonicalHints.Count; i++)
|
||||
{
|
||||
if (partitionsOfCanonicalHints[i].Contains(hint))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static FilledAutofillFieldCollection<FieldT> FilterForPartition<FieldT>(FilledAutofillFieldCollection<FieldT> autofillFields, int partitionIndex) where FieldT: InputField
|
||||
{
|
||||
FilledAutofillFieldCollection<FieldT> filteredCollection =
|
||||
new FilledAutofillFieldCollection<FieldT> { DatasetName = autofillFields.DatasetName };
|
||||
|
||||
if (partitionIndex == -1)
|
||||
return filteredCollection;
|
||||
|
||||
foreach (var field in autofillFields.HintMap.Values.Distinct())
|
||||
{
|
||||
foreach (var hint in field.AutofillHints)
|
||||
{
|
||||
if (GetPartitionIndex(hint) == partitionIndex)
|
||||
{
|
||||
filteredCollection.Add(field);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredCollection;
|
||||
}
|
||||
|
||||
public static FilledAutofillFieldCollection<FieldT> FilterForPartition<FieldT>(FilledAutofillFieldCollection<FieldT> filledAutofillFieldCollection, List<string> autofillFieldsFocusedAutofillCanonicalHints) where FieldT: InputField
|
||||
{
|
||||
|
||||
//only apply partition data if we have FocusedAutofillCanonicalHints. This may be empty on buggy Firefox.
|
||||
if (autofillFieldsFocusedAutofillCanonicalHints.Any())
|
||||
{
|
||||
int partitionIndex = AutofillHintsHelper.GetPartitionIndex(autofillFieldsFocusedAutofillCanonicalHints.FirstOrDefault());
|
||||
return AutofillHintsHelper.FilterForPartition(filledAutofillFieldCollection, partitionIndex);
|
||||
}
|
||||
|
||||
return filledAutofillFieldCollection;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// This enum represents the Android.Text.InputTypes values. For testability, this is duplicated here.
|
||||
/// </summary>
|
||||
public enum InputTypes
|
||||
{
|
||||
ClassDatetime = 4,
|
||||
ClassNumber = 2,
|
||||
ClassPhone = 3,
|
||||
ClassText = 1,
|
||||
DatetimeVariationDate = 16,
|
||||
DatetimeVariationNormal = 0,
|
||||
DatetimeVariationTime = 32,
|
||||
MaskClass = 15,
|
||||
MaskFlags = 16773120,
|
||||
MaskVariation = 4080,
|
||||
Null = 0,
|
||||
NumberFlagDecimal = 8192,
|
||||
NumberFlagSigned = 4096,
|
||||
NumberVariationNormal = 0,
|
||||
NumberVariationPassword = 16,
|
||||
TextFlagAutoComplete = 65536,
|
||||
TextFlagAutoCorrect = 32768,
|
||||
TextFlagCapCharacters = 4096,
|
||||
TextFlagCapSentences = 16384,
|
||||
TextFlagCapWords = 8192,
|
||||
TextFlagEnableTextConversionSuggestions = 1048576,
|
||||
TextFlagImeMultiLine = 262144,
|
||||
TextFlagMultiLine = 131072,
|
||||
TextFlagNoSuggestions = 524288,
|
||||
TextVariationEmailAddress = 32,
|
||||
TextVariationEmailSubject = 48,
|
||||
TextVariationFilter = 176,
|
||||
TextVariationLongMessage = 80,
|
||||
TextVariationNormal = 0,
|
||||
TextVariationPassword = 128,
|
||||
TextVariationPersonName = 96,
|
||||
TextVariationPhonetic = 192,
|
||||
TextVariationPostalAddress = 112,
|
||||
TextVariationShortMessage = 64,
|
||||
TextVariationUri = 16,
|
||||
TextVariationVisiblePassword = 144,
|
||||
TextVariationWebEditText = 160,
|
||||
TextVariationWebEmailAddress = 208,
|
||||
TextVariationWebPassword = 224
|
||||
}
|
||||
|
||||
public interface IKp2aDigitalAssetLinksDataSource
|
||||
{
|
||||
bool IsTrustedApp(string packageName);
|
||||
bool IsTrustedLink(string domain, string targetPackage);
|
||||
bool IsEnabled();
|
||||
|
||||
}
|
||||
|
||||
class TimeUtil
|
||||
{
|
||||
private static DateTime? m_dtUnixRoot = null;
|
||||
public static DateTime ConvertUnixTime(double dtUnix)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!m_dtUnixRoot.HasValue)
|
||||
m_dtUnixRoot = (new DateTime(1970, 1, 1, 0, 0, 0, 0,
|
||||
DateTimeKind.Utc)).ToLocalTime();
|
||||
|
||||
return m_dtUnixRoot.Value.AddSeconds(dtUnix);
|
||||
}
|
||||
catch (Exception) { Debug.Assert(false); }
|
||||
|
||||
return DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
public class FilledAutofillField<FieldT> where FieldT : InputField
|
||||
{
|
||||
private string[] _autofillHints;
|
||||
public string TextValue { get; set; }
|
||||
public long? DateValue { get; set; }
|
||||
public bool? ToggleValue { get; set; }
|
||||
|
||||
public string ValueToString()
|
||||
{
|
||||
if (DateValue != null)
|
||||
{
|
||||
return TimeUtil.ConvertUnixTime((long)DateValue / 1000.0).ToLongDateString();
|
||||
}
|
||||
if (ToggleValue != null)
|
||||
return ToggleValue.ToString();
|
||||
return TextValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the autofill hints for the filled field. These are always lowercased for simpler string comparison.
|
||||
/// </summary>
|
||||
public string[] AutofillHints
|
||||
{
|
||||
get
|
||||
{
|
||||
return _autofillHints;
|
||||
}
|
||||
set
|
||||
{
|
||||
_autofillHints = value;
|
||||
for (int i = 0; i < _autofillHints.Length; i++)
|
||||
_autofillHints[i] = _autofillHints[i].ToLower();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public FilledAutofillField()
|
||||
{ }
|
||||
|
||||
public FilledAutofillField(FieldT inputField)
|
||||
: this(inputField, inputField.AutofillHints)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public FilledAutofillField(FieldT inputField, string[] hints)
|
||||
{
|
||||
|
||||
string[] rawHints = AutofillHintsHelper.FilterForSupportedHints(hints);
|
||||
List<string> hintList = new List<string>();
|
||||
|
||||
string nextHint = null;
|
||||
for (int i = 0; i < rawHints.Length; i++)
|
||||
{
|
||||
string hint = rawHints[i];
|
||||
if (i < rawHints.Length - 1)
|
||||
{
|
||||
nextHint = rawHints[i + 1];
|
||||
}
|
||||
// First convert the compound W3C autofill hints
|
||||
if (W3cHints.isW3cSectionPrefix(hint) && i < rawHints.Length - 1)
|
||||
{
|
||||
hint = rawHints[++i];
|
||||
|
||||
if (i < rawHints.Length - 1)
|
||||
{
|
||||
nextHint = rawHints[i + 1];
|
||||
}
|
||||
}
|
||||
if (W3cHints.isW3cTypePrefix(hint) && nextHint != null && W3cHints.isW3cTypeHint(nextHint))
|
||||
{
|
||||
hint = nextHint;
|
||||
i++;
|
||||
|
||||
}
|
||||
if (W3cHints.isW3cAddressType(hint) && nextHint != null)
|
||||
{
|
||||
hint = nextHint;
|
||||
i++;
|
||||
|
||||
}
|
||||
|
||||
// Then check if the "actual" hint is supported.
|
||||
if (AutofillHintsHelper.IsSupportedHint(hint))
|
||||
{
|
||||
hintList.Add(hint);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
AutofillHints = AutofillHintsHelper.ConvertToCanonicalHints(hintList.ToArray()).ToArray();
|
||||
|
||||
|
||||
}
|
||||
|
||||
public bool IsNull()
|
||||
{
|
||||
return TextValue == null && DateValue == null && ToggleValue == null;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj == null || GetType() != obj.GetType()) return false;
|
||||
|
||||
FilledAutofillField<FieldT> that = (FilledAutofillField<FieldT>)obj;
|
||||
|
||||
if (!TextValue?.Equals(that.TextValue) ?? that.TextValue != null)
|
||||
return false;
|
||||
if (DateValue != null ? !DateValue.Equals(that.DateValue) : that.DateValue != null)
|
||||
return false;
|
||||
return ToggleValue != null ? ToggleValue.Equals(that.ToggleValue) : that.ToggleValue == null;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var result = TextValue != null ? TextValue.GetHashCode() : 0;
|
||||
result = 31 * result + (DateValue != null ? DateValue.GetHashCode() : 0);
|
||||
result = 31 * result + (ToggleValue != null ? ToggleValue.GetHashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for everything that is a input field which might (or might not) be autofilled.
|
||||
/// For testability, this is independent from Android classes like ViewNode
|
||||
/// </summary>
|
||||
public abstract class InputField
|
||||
{
|
||||
public string IdEntry { get; set; }
|
||||
public string Hint { get; set; }
|
||||
public string ClassName { get; set; }
|
||||
public string[] AutofillHints { get; set; }
|
||||
public bool IsFocused { get; set; }
|
||||
|
||||
public InputTypes InputType { get; set; }
|
||||
|
||||
public string HtmlInfoTag { get; set; }
|
||||
public string HtmlInfoTypeAttribute { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class AutofillView<TField> where TField : InputField
|
||||
{
|
||||
public List<TField> InputFields { get; set; } = new List<TField>();
|
||||
|
||||
public string PackageId { get; set; } = null;
|
||||
public string WebDomain { get; set; } = null;
|
||||
}
|
||||
|
||||
public interface ILogger
|
||||
{
|
||||
void Log(string x);
|
||||
}
|
||||
|
||||
public class StructureParserBase<FieldT> where FieldT: InputField
|
||||
{
|
||||
private readonly ILogger _log;
|
||||
private readonly IKp2aDigitalAssetLinksDataSource _digitalAssetLinksDataSource;
|
||||
|
||||
private readonly List<string> _autofillHintsForLogin = new List<string>
|
||||
{
|
||||
AutofillHintsHelper.AutofillHintPassword,
|
||||
AutofillHintsHelper.AutofillHintUsername,
|
||||
AutofillHintsHelper.AutofillHintEmailAddress
|
||||
};
|
||||
|
||||
public string PackageId { get; set; }
|
||||
|
||||
public Dictionary<FieldT, string[]> FieldsMappedToHints = new Dictionary<FieldT, string[]>();
|
||||
|
||||
public StructureParserBase(ILogger logger, IKp2aDigitalAssetLinksDataSource digitalAssetLinksDataSource)
|
||||
{
|
||||
_log = logger;
|
||||
_digitalAssetLinksDataSource = digitalAssetLinksDataSource;
|
||||
}
|
||||
|
||||
public class AutofillTargetId
|
||||
{
|
||||
public string PackageName { get; set; }
|
||||
|
||||
public string PackageNameWithPseudoSchema
|
||||
{
|
||||
get { return AndroidAppScheme + PackageName; }
|
||||
}
|
||||
|
||||
public const string AndroidAppScheme = "androidapp://";
|
||||
|
||||
public string WebDomain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If PackageName and WebDomain are not compatible (by DAL or because PackageName is a trusted browser in which case we treat all domains as "compatible"
|
||||
/// we need to issue a warning. If we would fill credentials for the package, a malicious website could try to get credentials for the app.
|
||||
/// If we would fill credentials for the domain, a malicious app could get credentials for the domain.
|
||||
/// </summary>
|
||||
public bool IncompatiblePackageAndDomain { get; set; }
|
||||
|
||||
public string DomainOrPackage
|
||||
{
|
||||
get
|
||||
{
|
||||
return WebDomain ?? PackageNameWithPseudoSchema;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AutofillTargetId ParseForFill(bool isManual, AutofillView<FieldT> autofillView)
|
||||
{
|
||||
return Parse(true, isManual, autofillView);
|
||||
}
|
||||
|
||||
public AutofillTargetId ParseForSave(AutofillView<FieldT> autofillView)
|
||||
{
|
||||
return Parse(false, true, autofillView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traverse AssistStructure and add ViewNode metadata to a flat list.
|
||||
/// </summary>
|
||||
/// <returns>The parse.</returns>
|
||||
/// <param name="forFill">If set to <c>true</c> for fill.</param>
|
||||
/// <param name="isManualRequest"></param>
|
||||
protected virtual AutofillTargetId Parse(bool forFill, bool isManualRequest, AutofillView<FieldT> autofillView)
|
||||
{
|
||||
AutofillTargetId result = new AutofillTargetId()
|
||||
{
|
||||
PackageName = autofillView.PackageId,
|
||||
WebDomain = autofillView.WebDomain
|
||||
};
|
||||
|
||||
_log.Log("parsing autofillStructure...");
|
||||
|
||||
//TODO remove from production
|
||||
_log.Log("will log the autofillStructure...");
|
||||
string debugInfo = JsonConvert.SerializeObject(autofillView, Newtonsoft.Json.Formatting.Indented);
|
||||
_log.Log("will log the autofillStructure... size is " + debugInfo.Length);
|
||||
_log.Log("This is the autofillStructure: \n\n " + debugInfo);
|
||||
|
||||
//go through each input field and determine username/password fields.
|
||||
//Depending on the target this can require more or less heuristics.
|
||||
// * if there is a valid & supported autofill hint, we assume that all fields which should be filled do have an appropriate Autofill hint
|
||||
// * if there is no such autofill hint, we use IsPassword to
|
||||
|
||||
HashSet<string> autofillHintsOfAllFields = autofillView.InputFields.Where(f => f.AutofillHints != null)
|
||||
.SelectMany(f => f.AutofillHints).ToHashSet();
|
||||
bool hasLoginAutofillHints = autofillHintsOfAllFields.Intersect(_autofillHintsForLogin).Any();
|
||||
|
||||
if (hasLoginAutofillHints)
|
||||
{
|
||||
foreach (var viewNode in autofillView.InputFields)
|
||||
{
|
||||
string[] viewHints = viewNode.AutofillHints;
|
||||
if (viewHints == null)
|
||||
continue;
|
||||
if (viewHints.Intersect(_autofillHintsForLogin).Any())
|
||||
{
|
||||
FieldsMappedToHints.Add(viewNode, viewHints);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//determine password fields, first by type, then by hint:
|
||||
List<FieldT> passwordFields = autofillView.InputFields.Where(f => IsEditText(f) && IsPassword(f)).ToList();
|
||||
if (!passwordFields.Any())
|
||||
{
|
||||
passwordFields = autofillView.InputFields.Where(f => IsEditText(f) && HasPasswordHint(f)).ToList();
|
||||
}
|
||||
|
||||
//determine username fields. Try by hint, if that fails use the one before the password
|
||||
List<FieldT> usernameFields = autofillView.InputFields.Where(f => IsEditText(f) && HasUsernameHint(f)).ToList();
|
||||
if (!usernameFields.Any())
|
||||
{
|
||||
foreach (var passwordField in passwordFields)
|
||||
{
|
||||
var lastInputBeforePassword = autofillView.InputFields
|
||||
.TakeWhile(f => IsEditText(f) && f != passwordField && !passwordFields.Contains(f)).LastOrDefault();
|
||||
if (lastInputBeforePassword != null)
|
||||
usernameFields.Add(lastInputBeforePassword);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//for "heuristic determination" we demand that one of the filled fields is focused:
|
||||
if (passwordFields.Concat(usernameFields).Any(f => f.IsFocused))
|
||||
{
|
||||
foreach (var uf in usernameFields)
|
||||
FieldsMappedToHints.Add(uf, new string[] { AutofillHintsHelper.AutofillHintUsername });
|
||||
foreach (var pf in passwordFields)
|
||||
FieldsMappedToHints.Add(pf, new string[] { AutofillHintsHelper.AutofillHintPassword });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(autofillView.WebDomain) && _digitalAssetLinksDataSource.IsEnabled())
|
||||
{
|
||||
result.IncompatiblePackageAndDomain = !_digitalAssetLinksDataSource.IsTrustedLink(autofillView.WebDomain, result.PackageName);
|
||||
if (result.IncompatiblePackageAndDomain)
|
||||
{
|
||||
_log.Log($"DAL verification failed for {result.PackageName}/{result.WebDomain}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.IncompatiblePackageAndDomain = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool IsEditText(FieldT f)
|
||||
{
|
||||
return (f.ClassName == "android.widget.EditText"
|
||||
|| f.ClassName == "android.widget.AutoCompleteTextView"
|
||||
|| f.HtmlInfoTag == "input");
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _passwordHints = new HashSet<string> { "password", "passwort"
|
||||
/*, "passwordAuto", "pswd"*/ };
|
||||
private static bool HasPasswordHint(InputField f)
|
||||
{
|
||||
return IsAny(f.IdEntry, _passwordHints) ||
|
||||
IsAny(f.Hint, _passwordHints);
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _usernameHints = new HashSet<string> { "email", "e-mail", "username" };
|
||||
|
||||
private static bool HasUsernameHint(InputField f)
|
||||
{
|
||||
return IsAny(f.IdEntry, _usernameHints) ||
|
||||
IsAny(f.Hint, _usernameHints);
|
||||
}
|
||||
|
||||
private static bool IsAny(string value, IEnumerable<string> terms)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var lowerValue = value.ToLowerInvariant();
|
||||
return terms.Any(t => lowerValue == t);
|
||||
}
|
||||
|
||||
private static bool IsInputTypeClass(InputTypes inputType, InputTypes inputTypeClass)
|
||||
{
|
||||
if (!InputTypes.MaskClass.HasFlag(inputTypeClass))
|
||||
throw new Exception("invalid inputTypeClass");
|
||||
return (((int)inputType) & (int)InputTypes.MaskClass) == (int)(inputTypeClass);
|
||||
}
|
||||
private static bool IsInputTypeVariation(InputTypes inputType, InputTypes inputTypeVariation)
|
||||
{
|
||||
if (!InputTypes.MaskVariation.HasFlag(inputTypeVariation))
|
||||
throw new Exception("invalid inputTypeVariation");
|
||||
return (((int)inputType) & (int)InputTypes.MaskVariation) == (int)(inputTypeVariation);
|
||||
}
|
||||
|
||||
private static bool IsPassword(InputField f)
|
||||
{
|
||||
InputTypes inputType = f.InputType;
|
||||
|
||||
return
|
||||
(!f.IdEntry?.ToLowerInvariant().Contains("search") ?? true) &&
|
||||
(!f.Hint?.ToLowerInvariant().Contains("search") ?? true) &&
|
||||
(
|
||||
(IsInputTypeClass(inputType, InputTypes.ClassText)
|
||||
&&
|
||||
(
|
||||
IsInputTypeVariation(inputType, InputTypes.TextVariationPassword)
|
||||
|| IsInputTypeVariation(inputType, InputTypes.TextVariationVisiblePassword)
|
||||
|| IsInputTypeVariation(inputType, InputTypes.TextVariationWebPassword)
|
||||
)
|
||||
)
|
||||
|| (f.AutofillHints != null && f.AutofillHints.First() == "passwordAuto")
|
||||
|| (f.HtmlInfoTypeAttribute == "password")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user