프로젝트

일반

사용자정보

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

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

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

1
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

    
110
                    isValidAppcast = VerifyAppCast(appcast, signature);
111
                }
112
                if (isValidAppcast)
113
                {
114
                    _logWriter.PrintMessage("Appcast is valid! Parsing...");
115
                    ParseAppCast(appcast);
116
                    return true;
117
                }
118
            }
119
            catch (Exception e)
120
            {
121
                _logWriter.PrintMessage("Error reading app cast {0}: {1} ", _castUrl, e.Message);
122
            }
123
            _logWriter.PrintMessage("Appcast is not valid");
124
            return false;
125
        }
126

    
127
        private bool VerifyAppCast(string appcast, string signature)
128
        {
129
            if (appcast == null)
130
            {
131
                _logWriter.PrintMessage("Cannot read response from URL {0}", _castUrl);
132
                return false;
133
            }
134

    
135
            // checking signature
136
            var signatureNeeded = Utilities.IsSignatureNeeded(_signatureVerifier.SecurityMode, _signatureVerifier.HasValidKeyInformation(), false);
137
            var appcastBytes = _dataDownloader.GetAppCastEncoding().GetBytes(appcast);
138
            if (signatureNeeded && _signatureVerifier.VerifySignature(signature, appcastBytes) == ValidationResult.Invalid)
139
            {
140
                _logWriter.PrintMessage("Signature check of appcast failed");
141
                return false;
142
            }
143
            return true;
144
        }
145

    
146
        /// <summary>
147
        /// Parse the app cast XML string into a list of <see cref="AppCastItem"/> objects.
148
        /// When complete, the Items list should contain the parsed information
149
        /// as <see cref="AppCastItem"/> objects.
150
        /// </summary>
151
        /// <param name="appcast">the non-null string XML app cast</param>
152
        private void ParseAppCast(string appcast)
153
        {
154
            const string itemNode = "item";
155
            Items.Clear();
156

    
157
            XDocument doc = XDocument.Parse(appcast);
158
            var rss = doc?.Element("rss");
159
            var channel = rss?.Element("channel");
160

    
161
            Title = channel?.Element("title")?.Value ?? string.Empty;
162
            Language = channel?.Element("language")?.Value ?? "en";
163

    
164
            var items = doc.Descendants(itemNode);
165
            foreach (var item in items)
166
            {
167
                var currentItem = AppCastItem.Parse(_config.InstalledVersion, _config.ApplicationName, _castUrl, item, _logWriter);
168
                _logWriter.PrintMessage("Found an item in the app cast: version {0} ({1}) -- os = {2}", 
169
                    currentItem?.Version, currentItem?.ShortVersion, currentItem.OperatingSystemString);
170
                Items.Add(currentItem);
171
            }
172

    
173
            // sort versions in reverse order
174
            Items.Sort((item1, item2) => -1 * item1.CompareTo(item2));
175
        }
176

    
177
        /// <summary>
178
        /// Returns sorted list of updates between current and latest. Installed is not included.
179
        /// </summary>
180
        public virtual List<AppCastItem> GetAvailableUpdates()
181
        {
182
            Version installed = new Version(_config.InstalledVersion);
183
            var signatureNeeded = Utilities.IsSignatureNeeded(_signatureVerifier.SecurityMode, _signatureVerifier.HasValidKeyInformation(), false);
184
            return Items.Where((item) =>
185
            {
186
#if NETFRAMEWORK
187
                // don't allow non-windows updates
188
                if (!item.IsWindowsUpdate)
189
                {
190
                    return false;
191
                }
192
#else
193
                // check operating system and filter out ones that don't match the current
194
                // operating system
195
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !item.IsWindowsUpdate)
196
                {
197
                    return false;
198
                }
199
                else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && !item.IsMacOSUpdate)
200
                {
201
                    return false;
202
                }
203
                else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !item.IsLinuxUpdate)
204
                {
205
                    return false;
206
                }
207
#endif
208
                // filter smaller versions
209
                if (new Version(item.Version).CompareTo(installed) <= 0)
210
                {
211
                    return false;
212
                }
213
                // filter versions without signature if we need signatures. But accept version without downloads.
214
                if (signatureNeeded && string.IsNullOrEmpty(item.DownloadSignature) && !string.IsNullOrEmpty(item.DownloadLink))
215
                {
216
                    return false;
217
                }
218
                // accept everything else
219
                return true;
220
            }).ToList();
221
        }
222

    
223
        /// <summary>
224
        /// Create AppCast XML
225
        /// </summary>
226
        /// <param name="items">The AppCastItems to include in the AppCast</param>
227
        /// <param name="title">AppCast application title</param>
228
        /// <param name="link">AppCast link</param>
229
        /// <param name="description">AppCast description</param>
230
        /// <param name="language">AppCast language</param>
231
        /// <returns>AppCast xml document</returns>
232
        public static XDocument GenerateAppCastXml(List<AppCastItem> items, string title, string link = "", string description = "", string language = "en")
233
        {
234
            var channel = new XElement("channel");
235
            channel.Add(new XElement("title", title));
236

    
237
            if (!string.IsNullOrEmpty(link))
238
            {
239
                channel.Add(new XElement("link", link));
240
            }
241

    
242
            if (!string.IsNullOrEmpty(description))
243
            {
244
                channel.Add(new XElement("description", description));
245
            }
246

    
247
            channel.Add(new XElement("language", language));
248

    
249
            foreach (var item in items)
250
            {
251
                channel.Add(item.GetXElement());
252
            }
253

    
254
            var document = new XDocument(
255
                new XElement("rss", new XAttribute("version", "2.0"), new XAttribute(XNamespace.Xmlns + "sparkle", SparkleNamespace),
256
                    channel)
257
            );
258

    
259
            return document;
260
        }
261
    }
262
}
클립보드 이미지 추가 (최대 크기: 500 MB)