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