using public suffix to determine canonical domains which fixes the aliexpress issue (fixes https://github.com/PhilippC/keepass2android/issues/711)
This commit is contained in:
12742
src/keepass2android/Assets/publicsuffix.txt
Normal file
12742
src/keepass2android/Assets/publicsuffix.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -248,6 +248,7 @@
|
||||
<Compile Include="services\AutofillBase\AutofillServiceBase.cs" />
|
||||
<Compile Include="services\AutofillBase\ChooseForAutofillActivityBase.cs" />
|
||||
<Compile Include="services\AutofillBase\CommonUtil.cs" />
|
||||
<Compile Include="services\AutofillBase\DomainParser.cs" />
|
||||
<Compile Include="services\AutofillBase\Kp2aDigitalAssetLinksDataSource.cs" />
|
||||
<Compile Include="services\AutofillBase\model\FilledAutofillField.cs" />
|
||||
<Compile Include="services\AutofillBase\model\FilledAutofillFieldCollection.cs" />
|
||||
@@ -1910,6 +1911,9 @@
|
||||
<SubType>Designer</SubType>
|
||||
</AndroidResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidAsset Include="Assets\publicsuffix.txt" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" />
|
||||
<Target Name="EnsureBclBuildImported" BeforeTargets="BeforeBuild" Condition="'$(BclBuildImported)' == ''">
|
||||
|
||||
@@ -87,7 +87,9 @@ namespace keepass2android.services.AutofillBase
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.OnSuccess(null);
|
||||
var builder = new FillResponse.Builder();
|
||||
builder.SetClientState(new Bundle());
|
||||
callback.OnSuccess(builder.Build());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
252
src/keepass2android/services/AutofillBase/DomainParser.cs
Normal file
252
src/keepass2android/services/AutofillBase/DomainParser.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.Res;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
|
||||
namespace DomainNameParser
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
public class DomainName
|
||||
{
|
||||
public DomainName(string rawDomainName, string publicSuffix, string registerableDomainName)
|
||||
{
|
||||
this.RawDomainName = rawDomainName;
|
||||
this.PublicSuffix = publicSuffix;
|
||||
this.RegisterableDomainName = registerableDomainName;
|
||||
}
|
||||
|
||||
public string RawDomainName { get; private set; }
|
||||
|
||||
public string PublicSuffix { get; private set; }
|
||||
|
||||
public string RegisterableDomainName { get; private set; }
|
||||
|
||||
public static bool TryParse(string rawDomainName, PublicSuffixRuleCache ruleCache, out DomainName domainName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rawDomainName) || !rawDomainName.Contains('.') || rawDomainName.StartsWith("."))
|
||||
{
|
||||
domainName = new DomainName(rawDomainName, null, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
rawDomainName = rawDomainName.ToLower();
|
||||
|
||||
// Split our domain into parts (based on the '.')
|
||||
// We'll be checking rules from the right-most part of the domain
|
||||
var domainLabels = rawDomainName.Trim().Split('.').ToList();
|
||||
domainLabels.Reverse();
|
||||
|
||||
// If no rules match, the prevailing rule is "*"
|
||||
var prevailingRule = FindMatchingRule(domainLabels, ruleCache) ?? new PublicSuffixRule("*");
|
||||
|
||||
// If the prevailing rule is an exception rule, modify it by removing the leftmost label.
|
||||
if (prevailingRule.Type == PublicSuffixRule.RuleType.Exception)
|
||||
{
|
||||
var labels = prevailingRule.Labels;
|
||||
labels.Reverse();
|
||||
labels.RemoveAt(0);
|
||||
|
||||
prevailingRule = new PublicSuffixRule(string.Join(".", labels));
|
||||
}
|
||||
|
||||
// The public suffix is the set of labels from the domain which directly match the labels of the prevailing rule (joined by dots).
|
||||
var publicSuffix = Enumerable.Range(0, prevailingRule.Labels.Count).Aggregate(string.Empty, (current, i) => string.Format("{0}.{1}", domainLabels[i], current).Trim('.'));
|
||||
|
||||
// The registered or registrable domain is the public suffix plus one additional label.
|
||||
var registrableDomain = string.Format("{0}.{1}", domainLabels[prevailingRule.Labels.Count], publicSuffix);
|
||||
|
||||
domainName = new DomainName(rawDomainName, publicSuffix, registrableDomain);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
domainName = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static PublicSuffixRule FindMatchingRule(List<string> domainLabels, PublicSuffixRuleCache ruleCache)
|
||||
{
|
||||
var ruleMatches = ruleCache.PublicSuffixRules.Where(r => r.AppliesTo(domainLabels)).ToList();
|
||||
|
||||
// If there is only one match, return it.
|
||||
if (ruleMatches.Count() == 1)
|
||||
{
|
||||
return ruleMatches[0];
|
||||
}
|
||||
|
||||
// If more than one rule matches, the prevailing rule is the one which is an exception rule.
|
||||
var exceptionRules = ruleMatches.Where(r => r.Type == PublicSuffixRule.RuleType.Exception).ToList();
|
||||
if (exceptionRules.Count() == 1)
|
||||
{
|
||||
return exceptionRules[0];
|
||||
}
|
||||
if (exceptionRules.Count() > 1)
|
||||
{
|
||||
throw new ApplicationException("Unexpectedly found multiple matching exception rules.");
|
||||
}
|
||||
|
||||
// If there is no matching exception rule, the prevailing rule is the one with the most labels.
|
||||
var prevailingRule = ruleMatches.OrderByDescending(r => r.Labels.Count).Take(1).SingleOrDefault();
|
||||
return prevailingRule;
|
||||
}
|
||||
}
|
||||
|
||||
public class PublicSuffixRule
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a rule based on a single line from the www.publicsuffix.org list
|
||||
/// </summary>
|
||||
/// <param name="ruleLine">The rule line.</param>
|
||||
public PublicSuffixRule(string ruleLine)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ruleLine) || string.IsNullOrWhiteSpace(ruleLine))
|
||||
{
|
||||
throw new ArgumentNullException("ruleLine");
|
||||
}
|
||||
|
||||
// Parse the rule and set properties accordingly:
|
||||
string ruleName;
|
||||
|
||||
if (ruleLine.StartsWith("*", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
this.Type = RuleType.Wildcard;
|
||||
ruleName = ruleLine;
|
||||
}
|
||||
else if (ruleLine.StartsWith("!", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
this.Type = RuleType.Exception;
|
||||
ruleName = ruleLine.Substring(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Type = RuleType.Normal;
|
||||
ruleName = ruleLine;
|
||||
}
|
||||
|
||||
this.Name = ruleName.Split(' ')[0];
|
||||
|
||||
var labels = this.Name.Split('.').ToList();
|
||||
labels.Reverse();
|
||||
|
||||
this.Labels = labels;
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public RuleType Type { get; private set; }
|
||||
|
||||
public List<string> Labels { get; private set; }
|
||||
|
||||
public bool AppliesTo(List<string> domainLabels)
|
||||
{
|
||||
if (this.Labels.Count > domainLabels.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var position in Enumerable.Range(0, this.Labels.Count))
|
||||
{
|
||||
if (this.Labels[position] == "*")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.Labels[position] != domainLabels[position])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rule type
|
||||
/// </summary>
|
||||
public enum RuleType
|
||||
{
|
||||
/// <summary>
|
||||
/// A normal rule
|
||||
/// </summary>
|
||||
Normal,
|
||||
|
||||
/// <summary>
|
||||
/// A wildcard rule, as defined by www.publicsuffix.org
|
||||
/// </summary>
|
||||
Wildcard,
|
||||
|
||||
/// <summary>
|
||||
/// An exception rule, as defined by www.publicsuffix.org
|
||||
/// </summary>
|
||||
Exception
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class PublicSuffixRuleCache
|
||||
{
|
||||
static IEnumerable<string> ReadLines(StreamReader reader,
|
||||
Encoding encoding)
|
||||
{
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
yield return line;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public PublicSuffixRuleCache(Context context)
|
||||
{
|
||||
|
||||
this.PublicSuffixRules = GetRules(context);
|
||||
}
|
||||
|
||||
public PublicSuffixRuleCache(IEnumerable<string> publicSuffixRules)
|
||||
{
|
||||
this.PublicSuffixRules = GetRules(publicSuffixRules);
|
||||
}
|
||||
|
||||
public List<PublicSuffixRule> PublicSuffixRules { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of TLD rules from the cache
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static List<PublicSuffixRule> GetRules(Context context)
|
||||
{
|
||||
AssetManager assets = context.Assets;
|
||||
using (StreamReader sr = new StreamReader(assets.Open("publicsuffix.txt")))
|
||||
{
|
||||
var ruleStrings = ReadLines(sr, Encoding.UTF8).ToList();
|
||||
return GetRules(ruleStrings);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<PublicSuffixRule> GetRules(IEnumerable<string> publicSuffixRules)
|
||||
{
|
||||
// Strip out any lines that are a comment or blank.
|
||||
return
|
||||
publicSuffixRules.Where(
|
||||
ruleString =>
|
||||
ruleString.Trim().Length != 0
|
||||
&& !ruleString.StartsWith("//", StringComparison.InvariantCultureIgnoreCase)).Select(ruleString => new PublicSuffixRule(ruleString)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using Android.Util;
|
||||
using Android.Views;
|
||||
using Android.Views.Autofill;
|
||||
using Android.Views.InputMethods;
|
||||
using DomainNameParser;
|
||||
using keepass2android.services.AutofillBase.model;
|
||||
using FilledAutofillFieldCollection = keepass2android.services.AutofillBase.model.FilledAutofillFieldCollection;
|
||||
|
||||
@@ -24,6 +25,7 @@ namespace keepass2android.services.AutofillBase
|
||||
public AutofillFieldMetadataCollection AutofillFields { get; set; }
|
||||
AssistStructure Structure;
|
||||
private List<AssistStructure.ViewNode> _editTextsWithoutHint = new List<AssistStructure.ViewNode>();
|
||||
private PublicSuffixRuleCache domainSuffixParserCache;
|
||||
public FilledAutofillFieldCollection ClientFormData { get; set; }
|
||||
|
||||
public StructureParser(Context context, AssistStructure structure)
|
||||
@@ -31,6 +33,7 @@ namespace keepass2android.services.AutofillBase
|
||||
mContext = context;
|
||||
Structure = structure;
|
||||
AutofillFields = new AutofillFieldMetadataCollection();
|
||||
domainSuffixParserCache = new PublicSuffixRuleCache(context);
|
||||
}
|
||||
|
||||
public string ParseForFill(bool isManual)
|
||||
@@ -190,10 +193,19 @@ namespace keepass2android.services.AutofillBase
|
||||
);
|
||||
}
|
||||
|
||||
void ParseLocked(bool forFill, bool isManualRequest, AssistStructure.ViewNode viewNode, ref string validWebdomain)
|
||||
|
||||
|
||||
void ParseLocked(bool forFill, bool isManualRequest, AssistStructure.ViewNode viewNode, ref string validWebdomain)
|
||||
{
|
||||
String webDomain = viewNode.WebDomain;
|
||||
if (webDomain != null)
|
||||
|
||||
DomainName outDomain;
|
||||
if (DomainName.TryParse(webDomain, domainSuffixParserCache, out outDomain))
|
||||
{
|
||||
webDomain = outDomain.RegisterableDomainName;
|
||||
}
|
||||
|
||||
if (webDomain != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(validWebdomain))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user