프로젝트

일반

사용자정보

통계
| 브랜치(Branch): | 개정판:

markus / MarkusAutoUpdate / src / NetSparkle / AppCastHandlers / XMLAppCast.cs @ d8f5045e

이력 | 보기 | 이력해설 | 다운로드 (10.2 KB)

1 d8f5045e taeseongkim
using NetSparkleUpdater.Configurations;
2
using NetSparkleUpdater.Enums;
3
using NetSparkleUpdater.Interfaces;
4
using System;
5
using System.Collections.Generic;
6
using System.Linq;
7
using System.Runtime.InteropServices;
8
using System.Xml.Linq;
9
10
namespace NetSparkleUpdater.AppCastHandlers
11
{
12
    /// <summary>
13
    /// An XML-based appcast document downloader and handler
14
    /// </summary>
15
    public class XMLAppCast : IAppCastHandler
16
    {
17
        private Configuration _config;
18
        private string _castUrl;
19
20
        private ISignatureVerifier _signatureVerifier;
21
        private ILogger _logWriter;
22
23
        private IAppCastDataDownloader _dataDownloader;
24
25
        /// <summary>
26
        /// Sparkle XML namespace
27
        /// </summary>
28
        public static readonly XNamespace SparkleNamespace = "http://www.andymatuschak.org/xml-namespaces/sparkle";
29
30
        /// <summary>
31
        /// AppCast Title
32
        /// </summary>
33
        public string Title { get; set; }
34
35
        /// <summary>
36
        /// AppCast Language
37
        /// </summary>
38
        public string Language { get; set; }
39
40
        /// <summary>
41
        /// AppCastItems from the appcast
42
        /// </summary>
43
        public readonly List<AppCastItem> Items;
44
45
        /// <summary>
46
        /// Constructor
47
        /// </summary>
48
        public XMLAppCast()
49
        {
50
            Items = new List<AppCastItem>();
51
        }
52
53
        /// <summary>
54
        /// Setup the app cast handler info for downloading and parsing app cast information
55
        /// </summary>
56
        /// <param name="dataDownloader">downloader that will manage the app cast download 
57
        /// (provided by <see cref="SparkleUpdater"/> via the 
58
        /// <see cref="SparkleUpdater.AppCastDataDownloader"/> property.</param>
59
        /// <param name="castUrl">full URL to the app cast file</param>
60
        /// <param name="config">configuration for handling update intervals/checks 
61
        /// (user skipped versions, etc.)</param>
62
        /// <param name="signatureVerifier">Object to check signatures of app cast information</param>
63
        /// <param name="logWriter">object that you can utilize to do any necessary logging</param>
64
        public void SetupAppCastHandler(IAppCastDataDownloader dataDownloader, string castUrl, Configuration config, ISignatureVerifier signatureVerifier, ILogger logWriter = null)
65
        {
66
            _dataDownloader = dataDownloader;
67
            _config = config;
68
            _castUrl = castUrl;
69
70
            _signatureVerifier = signatureVerifier;
71
            _logWriter = logWriter ?? new LogWriter();
72
        }
73
74
        /// <summary>
75
        /// Download castUrl resource and parse it
76
        /// </summary>
77
        public bool DownloadAndParse()
78
        {
79
            try
80
            {
81
                _logWriter.PrintMessage("Downloading app cast data...");
82
                var appcast = _dataDownloader.DownloadAndGetAppCastData(_castUrl);
83
                var signatureNeeded = Utilities.IsSignatureNeeded(_signatureVerifier.SecurityMode, _signatureVerifier.HasValidKeyInformation(), false);
84
                bool isValidAppcast = true;
85
                if (signatureNeeded)
86
                {
87
                    _logWriter.PrintMessage("Downloading app cast signature data...");
88
                    var signature = "";
89
                    try
90
                    {
91
                        signature = _dataDownloader.DownloadAndGetAppCastData(_castUrl + ".signature");
92
                    }
93
                    catch (Exception e)
94
                    {
95
                        _logWriter.PrintMessage("Error reading app cast {0}.signature: {1} ", _castUrl, e.Message);
96
                    }
97
                    if (string.IsNullOrWhiteSpace(signature))
98
                    {
99
                        // legacy: check for .dsa file
100
                        try
101
                        {
102
                            signature = _dataDownloader.DownloadAndGetAppCastData(_castUrl + ".dsa");
103
                        }
104
                        catch (Exception e)
105
                        {
106
                            _logWriter.PrintMessage("Error reading app cast {0}.dsa: {1} ", _castUrl, e.Message);
107
                        }
108
                    }
109
                    isValidAppcast = VerifyAppCast(appcast, signature);
110
                }
111
                if (isValidAppcast)
112
                {
113
                    _logWriter.PrintMessage("Appcast is valid! Parsing...");
114
                    ParseAppCast(appcast);
115
                    return true;
116
                }
117
            }
118
            catch (Exception e)
119
            {
120
                _logWriter.PrintMessage("Error reading app cast {0}: {1} ", _castUrl, e.Message);
121
            }
122
            _logWriter.PrintMessage("Appcast is not valid");
123
            return false;
124
        }
125
126
        private bool VerifyAppCast(string appcast, string signature)
127
        {
128
            if (appcast == null)
129
            {
130
                _logWriter.PrintMessage("Cannot read response from URL {0}", _castUrl);
131
                return false;
132
            }
133
134
            // checking signature
135
            var signatureNeeded = Utilities.IsSignatureNeeded(_signatureVerifier.SecurityMode, _signatureVerifier.HasValidKeyInformation(), false);
136
            var appcastBytes = _dataDownloader.GetAppCastEncoding().GetBytes(appcast);
137
            if (signatureNeeded && _signatureVerifier.VerifySignature(signature, appcastBytes) == ValidationResult.Invalid)
138
            {
139
                _logWriter.PrintMessage("Signature check of appcast failed");
140
                return false;
141
            }
142
            return true;
143
        }
144
145
        /// <summary>
146
        /// Parse the app cast XML string into a list of <see cref="AppCastItem"/> objects.
147
        /// When complete, the Items list should contain the parsed information
148
        /// as <see cref="AppCastItem"/> objects.
149
        /// </summary>
150
        /// <param name="appcast">the non-null string XML app cast</param>
151
        private void ParseAppCast(string appcast)
152
        {
153
            const string itemNode = "item";
154
            Items.Clear();
155
156
            XDocument doc = XDocument.Parse(appcast);
157
            var rss = doc?.Element("rss");
158
            var channel = rss?.Element("channel");
159
160
            Title = channel?.Element("title")?.Value ?? string.Empty;
161
            Language = channel?.Element("language")?.Value ?? "en";
162
163
            var items = doc.Descendants(itemNode);
164
            foreach (var item in items)
165
            {
166
                var currentItem = AppCastItem.Parse(_config.InstalledVersion, _config.ApplicationName, _castUrl, item, _logWriter);
167
                _logWriter.PrintMessage("Found an item in the app cast: version {0} ({1}) -- os = {2}", 
168
                    currentItem?.Version, currentItem?.ShortVersion, currentItem.OperatingSystemString);
169
                Items.Add(currentItem);
170
            }
171
172
            // sort versions in reverse order
173
            Items.Sort((item1, item2) => -1 * item1.CompareTo(item2));
174
        }
175
176
        /// <summary>
177
        /// Returns sorted list of updates between current and latest. Installed is not included.
178
        /// </summary>
179
        public virtual List<AppCastItem> GetAvailableUpdates()
180
        {
181
            Version installed = new Version(_config.InstalledVersion);
182
            var signatureNeeded = Utilities.IsSignatureNeeded(_signatureVerifier.SecurityMode, _signatureVerifier.HasValidKeyInformation(), false);
183
            return Items.Where((item) =>
184
            {
185
#if NETFRAMEWORK
186
                // don't allow non-windows updates
187
                if (!item.IsWindowsUpdate)
188
                {
189
                    return false;
190
                }
191
#else
192
                // check operating system and filter out ones that don't match the current
193
                // operating system
194
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !item.IsWindowsUpdate)
195
                {
196
                    return false;
197
                }
198
                else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && !item.IsMacOSUpdate)
199
                {
200
                    return false;
201
                }
202
                else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !item.IsLinuxUpdate)
203
                {
204
                    return false;
205
                }
206
#endif
207
                // filter smaller versions
208
                if (new Version(item.Version).CompareTo(installed) <= 0)
209
                {
210
                    return false;
211
                }
212
                // filter versions without signature if we need signatures. But accept version without downloads.
213
                if (signatureNeeded && string.IsNullOrEmpty(item.DownloadSignature) && !string.IsNullOrEmpty(item.DownloadLink))
214
                {
215
                    return false;
216
                }
217
                // accept everything else
218
                return true;
219
            }).ToList();
220
        }
221
222
        /// <summary>
223
        /// Create AppCast XML
224
        /// </summary>
225
        /// <param name="items">The AppCastItems to include in the AppCast</param>
226
        /// <param name="title">AppCast application title</param>
227
        /// <param name="link">AppCast link</param>
228
        /// <param name="description">AppCast description</param>
229
        /// <param name="language">AppCast language</param>
230
        /// <returns>AppCast xml document</returns>
231
        public static XDocument GenerateAppCastXml(List<AppCastItem> items, string title, string link = "", string description = "", string language = "en")
232
        {
233
            var channel = new XElement("channel");
234
            channel.Add(new XElement("title", title));
235
236
            if (!string.IsNullOrEmpty(link))
237
            {
238
                channel.Add(new XElement("link", link));
239
            }
240
241
            if (!string.IsNullOrEmpty(description))
242
            {
243
                channel.Add(new XElement("description", description));
244
            }
245
246
            channel.Add(new XElement("language", language));
247
248
            foreach (var item in items)
249
            {
250
                channel.Add(item.GetXElement());
251
            }
252
253
            var document = new XDocument(
254
                new XElement("rss", new XAttribute("version", "2.0"), new XAttribute(XNamespace.Xmlns + "sparkle", SparkleNamespace),
255
                    channel)
256
            );
257
258
            return document;
259
        }
260
    }
261
}
클립보드 이미지 추가 (최대 크기: 500 MB)