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 | } |