summaryrefslogtreecommitdiffstats
path: root/src/main.lib/Services/DomainParseService.cs
blob: a74dfca4650eba0f61f86835cdc46e8c1a5c82dc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
using Nager.PublicSuffix;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace PKISharp.WACS.Services
{
    public class DomainParseService
    {
        private const string Source = "https://publicsuffix.org/list/public_suffix_list.dat";
        private DomainParser? _parser;
        private readonly ILogService _log;
        private readonly ISettingsService _settings;
        private readonly ProxyService _proxy;

        private DomainParser Parser
        {
            get
            {
                if (_parser == null)
                {
                    var path = Path.Combine(Path.GetDirectoryName(_settings.ExePath), "public_suffix_list.dat");
                    try
                    {
                        _parser = new DomainParser(new FileTldRuleProvider(path));
                    }
                    catch (Exception ex)
                    {
                        _log.Warning("Error loading static public suffix list from {path}: {ex}", path, ex.Message);
                    }
                    try
                    {
                        _parser = new DomainParser(new WebTldRuleProvider(_proxy, _log, _settings));
                    }
                    catch (Exception ex)
                    {
                        _log.Warning("Error updating public suffix list from {source}: {ex}", Source, ex.Message);
                    }
                }
                if (_parser == null)
                {
                    throw new Exception("Public suffix list unavailable");
                }
                return _parser;
            }
        }

        public DomainParseService(ILogService log, ProxyService proxy, ISettingsService settings)
        {
            _log = log;
            _settings = settings;
            _proxy = proxy;
        }

        public string GetTLD(string fulldomain) => Parser.Get(fulldomain).TLD;
        public string GetDomain(string fulldomain) => Parser.Get(fulldomain).Domain;
        
        /// <summary>
        /// Regular 7 day file cache in the configuration folder
        /// </summary>
        private class FileCacheProvider : ICacheProvider
        {
            private readonly FileInfo? _file;
            private string? _memoryCache;
            private readonly ILogService _log;

            public FileCacheProvider(ILogService log, ISettingsService settings)
            {
                _log = log;
                var path = Path.Combine(settings.Client.ConfigurationPath, "public_suffix_list.dat");
                _file = new FileInfo(path);
            }

            public async Task<string> GetAsync()
            {
                if (_file != null)
                {
                    try
                    {
                        _memoryCache = await File.ReadAllTextAsync(_file.FullName);
                    }
                    catch (Exception ex)
                    {
                        _log.Warning("Unable to read public suffix list cache from {path}: {ex}", _file.FullName, ex.Message);
                    };
                }
                return _memoryCache ?? "";
            }

            public bool IsCacheValid()
            {
                if (_file != null)
                {
                    return _file.Exists && _file.LastWriteTimeUtc > DateTime.UtcNow.AddDays(-30);
                }
                else
                {
                    return !string.IsNullOrEmpty(_memoryCache);
                }
            }

            public async Task SetAsync(string val) 
            {
                if (_file != null)
                {
                    try
                    {
                        await File.WriteAllTextAsync(_file.FullName, val);
                    } 
                    catch (Exception ex)
                    {
                        _log.Warning("Unable to write public suffix list cache to {path}: {ex}", _file.FullName, ex.Message);
                    }
                }
                _memoryCache = val;
            }
        }

        /// <summary>
        /// Custom implementation so that we can use the proxy 
        /// that the user has configured and 
        /// </summary>
        private class WebTldRuleProvider : ITldRuleProvider
        {
            private readonly string _fileUrl;
            private readonly ProxyService _proxy;
            private readonly ICacheProvider _cache;

            public WebTldRuleProvider(ProxyService proxy, ILogService log, ISettingsService settings)
            {
                _fileUrl = "https://publicsuffix.org/list/public_suffix_list.dat";
                _cache = new FileCacheProvider(log, settings);
                _proxy = proxy;
            }

            public async Task<IEnumerable<TldRule>> BuildAsync()
            {
                var ruleParser = new TldRuleParser();
                string ruleData;
                if (!_cache.IsCacheValid())
                {
                    ruleData = await LoadFromUrl(_fileUrl);
                    await _cache.SetAsync(ruleData);
                }
                else
                {
                    ruleData = await _cache.GetAsync();
                }
                var rules = ruleParser.ParseRules(ruleData);
                return rules;
            }

            public async Task<string> LoadFromUrl(string url)
            {
                using var httpClient = _proxy.GetHttpClient();
                using var response = await httpClient.GetAsync(url);
                if (!response.IsSuccessStatusCode)
                {
                    throw new RuleLoadException($"Cannot load from {url} {response.StatusCode}");
                }
                return await response.Content.ReadAsStringAsync();
            }
        }

    }
}