27
27
#include " error.h"
28
28
29
29
#include < expat.h>
30
- #include < vector>
31
30
#include < algorithm>
31
+ #include < iterator>
32
+ #include < vector>
32
33
#include < windows.h>
33
34
34
35
namespace winsparkle
35
36
{
36
37
38
+ namespace
39
+ {
40
+
41
+ // OS identification strings:
42
+
43
+ #define OS_MARKER_GENERIC " windows"
44
+ #ifdef _WIN64
45
+ #if defined(__AARCH64EL__) || defined(_M_ARM64)
46
+ #define OS_MARKER_ARCH " windows-arm64"
47
+ #else
48
+ #define OS_MARKER_ARCH " windows-x64"
49
+ #endif // defined(__AARCH64EL__) || defined(_M_ARM64)
50
+ #else
51
+ #define OS_MARKER_ARCH " windows-x86"
52
+ #endif // _WIN64
53
+
54
+
55
+ // Misc helper functions:
56
+
57
+ bool is_compatible_with_windows_version (const Appcast &item)
58
+ {
59
+ if (item.MinOSVersion .empty ())
60
+ return true ;
61
+
62
+ OSVERSIONINFOEXW osvi = { sizeof (osvi), 0 , 0 , 0 , 0 , { 0 }, 0 , 0 };
63
+ DWORDLONG const dwlConditionMask = VerSetConditionMask (
64
+ VerSetConditionMask (
65
+ VerSetConditionMask (
66
+ 0 , VER_MAJORVERSION, VER_GREATER_EQUAL),
67
+ VER_MINORVERSION, VER_GREATER_EQUAL),
68
+ VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
69
+
70
+ sscanf (item.MinOSVersion .c_str (), " %lu.%lu.%hu" , &osvi.dwMajorVersion ,
71
+ &osvi.dwMinorVersion , &osvi.wServicePackMajor );
72
+
73
+ return VerifyVersionInfoW (&osvi, VER_MAJORVERSION | VER_MINORVERSION |
74
+ VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE ;
75
+ }
76
+
77
+
78
+ // Checks if the item is compatible with the running OS, that is:
79
+ // - is not for a different OS
80
+ // - is either for current architecture or is architecture-independent
81
+ //
82
+ // E.g. returns true if item is
83
+ // - "windows-arm64" on 64bit ARM
84
+ // - "windows-x64" on 64bit Intel/AMD
85
+ // - "windows-x86" on 32bit
86
+ // - "windows" on any Windows arch
87
+ // - empty string on any OS
88
+ inline bool is_compatible_with_os_arch (const Appcast::Enclosure& enclosure)
89
+ {
90
+ return enclosure.OS .empty () || enclosure.OS == OS_MARKER_GENERIC || enclosure.OS == OS_MARKER_ARCH;
91
+ }
92
+
93
+
94
+ // Finds the best enclosure for the current OS and architecture.
95
+ Appcast::Enclosure find_best_enclosure_for_os_arch (const std::vector<Appcast::Enclosure>& enclosures)
96
+ {
97
+ // filter out incompatible enclosures:
98
+ std::vector<Appcast::Enclosure> compatible;
99
+ std::copy_if (enclosures.begin (), enclosures.end (), std::back_inserter (compatible), is_compatible_with_os_arch);
100
+ if (compatible.empty ())
101
+ return Appcast::Enclosure ();
102
+
103
+ // is there arch-specific enclosure?
104
+ auto it = std::find_if (compatible.begin (), compatible.end (),
105
+ [](const Appcast::Enclosure& e) { return e.OS == OS_MARKER_ARCH; });
106
+ if (it != compatible.end ())
107
+ return *it;
108
+
109
+ // is there an enclosure explicitly marked as for windows?
110
+ it = std::find_if (compatible.begin (), compatible.end (),
111
+ [](const Appcast::Enclosure& e) { return e.OS == OS_MARKER_GENERIC; });
112
+ if (it != compatible.end ())
113
+ return *it;
114
+
115
+ // because all enclosures are compatible, any one will do, e.g. the first one:
116
+ return compatible.front ();
117
+ }
118
+
119
+
120
+ void trim_whitespace (std::string& s)
121
+ {
122
+ size_t startpos = s.find_first_not_of (" \t\r\n " );
123
+ if (startpos != std::string::npos)
124
+ s = s.substr (startpos);
125
+ size_t endpos = s.find_last_not_of (" \t\r\n " );
126
+ if (endpos != std::string::npos)
127
+ s = s.substr (0 , endpos + 1 );
128
+ }
129
+
130
+
37
131
/* --------------------------------------------------------------------------*
38
132
XML parsing
39
133
*--------------------------------------------------------------------------*/
40
134
41
- namespace
42
- {
43
-
44
135
#define MVAL (x ) x
45
136
#define CONCAT3 (a,b,c ) MVAL(a)##MVAL(b)##MVAL(c)
46
137
@@ -66,8 +157,7 @@ namespace
66
157
#define NODE_VERSION ATTR_VERSION // These can be nodes or
67
158
#define NODE_SHORTVERSION ATTR_SHORTVERSION // attributes.
68
159
#define NODE_DSASIGNATURE ATTR_DSASIGNATURE
69
- #define OS_MARKER " windows"
70
- #define OS_MARKER_LEN 7
160
+
71
161
72
162
// context data for the parser
73
163
struct ContextData
@@ -78,38 +168,36 @@ struct ContextData
78
168
in_version(0 ), in_shortversion(0 ), in_dsasignature(0 ), in_min_os_version(0 )
79
169
{}
80
170
171
+ // call when entering <item> element
172
+ void reset_for_new_item ()
173
+ {
174
+ current = Appcast ();
175
+ enclosures.clear ();
176
+ legacy_dsa_signature.clear ();
177
+ }
178
+
81
179
// the parser we're using
82
180
XML_Parser& parser;
83
181
84
182
// is inside <channel>, <item> or <sparkle:releaseNotesLink>, <title>, <description>, or <link> respectively?
85
183
int in_channel, in_item, in_relnotes, in_title, in_description, in_link;
86
184
87
- // is inside <sparkle:version> or <sparkle:shortVersionString> node?
185
+ // is inside <sparkle:version> or <sparkle:shortVersionString> etc. node?
88
186
int in_version, in_shortversion, in_dsasignature, in_min_os_version;
89
187
90
- // parsed <item>s
91
- std::vector<Appcast> items;
92
- };
93
-
94
- bool is_windows_version_acceptable (const Appcast &item)
95
- {
96
- if (item.MinOSVersion .empty ())
97
- return true ;
188
+ // currently parsed item
189
+ Appcast current;
98
190
99
- OSVERSIONINFOEXW osvi = { sizeof (osvi), 0 , 0 , 0 , 0 , { 0 }, 0 , 0 };
100
- DWORDLONG const dwlConditionMask = VerSetConditionMask (
101
- VerSetConditionMask (
102
- VerSetConditionMask (
103
- 0 , VER_MAJORVERSION, VER_GREATER_EQUAL),
104
- VER_MINORVERSION, VER_GREATER_EQUAL),
105
- VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
191
+ // enclosures encountered so far
192
+ std::vector<Appcast::Enclosure> enclosures;
106
193
107
- sscanf (item.MinOSVersion .c_str (), " %lu.%lu.%hu" , &osvi.dwMajorVersion ,
108
- &osvi.dwMinorVersion , &osvi.wServicePackMajor );
194
+ // signature present as <sparkle:dsaSignature>, not enclosure attribute
195
+ std::string legacy_dsa_signature;
196
+
197
+ // parsed <item>s
198
+ std::vector<Appcast> all_items;
199
+ };
109
200
110
- return VerifyVersionInfoW (&osvi, VER_MAJORVERSION | VER_MINORVERSION |
111
- VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE ;
112
- }
113
201
114
202
void XMLCALL OnStartElement (void *data, const char *name, const char **attrs)
115
203
{
@@ -122,8 +210,7 @@ void XMLCALL OnStartElement(void *data, const char *name, const char **attrs)
122
210
else if ( ctxt.in_channel && strcmp (name, NODE_ITEM) == 0 )
123
211
{
124
212
ctxt.in_item ++;
125
- Appcast item;
126
- ctxt.items .push_back (item);
213
+ ctxt.reset_for_new_item ();
127
214
}
128
215
else if ( ctxt.in_item )
129
216
{
@@ -161,80 +248,43 @@ void XMLCALL OnStartElement(void *data, const char *name, const char **attrs)
161
248
}
162
249
else if (strcmp (name, NODE_ENCLOSURE) == 0 )
163
250
{
164
- if (!ctxt.items .empty ())
251
+ Appcast& item = ctxt.current ;
252
+ Appcast::Enclosure enclosure;
253
+
254
+ for (int i = 0 ; attrs[i]; i += 2 )
165
255
{
166
- Appcast& item = ctxt.items .back ();
167
- for (int i = 0 ; attrs[i]; i += 2 )
168
- {
169
- const char * name = attrs[i];
170
- const char * value = attrs[i + 1 ];
171
-
172
- if (strcmp (name, ATTR_URL) == 0 )
173
- item.DownloadURL = value;
174
- else if (strcmp (name, ATTR_VERSION) == 0 )
175
- item.Version = value;
176
- else if (strcmp (name, ATTR_SHORTVERSION) == 0 )
177
- item.ShortVersionString = value;
178
- else if (strcmp (name, ATTR_DSASIGNATURE) == 0 )
179
- item.DsaSignature = value;
180
- else if (strcmp (name, ATTR_OS) == 0 )
181
- item.Os = value;
182
- else if (strcmp (name, ATTR_ARGUMENTS) == 0 )
183
- item.InstallerArguments = value;
184
- }
256
+ const char * name = attrs[i];
257
+ const char * value = attrs[i + 1 ];
258
+
259
+ if (strcmp (name, ATTR_URL) == 0 )
260
+ enclosure.DownloadURL = value;
261
+ else if (strcmp (name, ATTR_DSASIGNATURE) == 0 )
262
+ enclosure.DsaSignature = value;
263
+ else if (strcmp (name, ATTR_OS) == 0 )
264
+ enclosure.OS = value;
265
+ else if (strcmp (name, ATTR_ARGUMENTS) == 0 )
266
+ enclosure.InstallerArguments = value;
267
+
268
+ // legacy syntax where version info was on enclosure, not item:
269
+ else if (strcmp (name, ATTR_VERSION) == 0 )
270
+ item.Version = value;
271
+ else if (strcmp (name, ATTR_SHORTVERSION) == 0 )
272
+ item.ShortVersionString = value;
185
273
}
274
+
275
+ // note: we intentionally include incompatible enclosures in the list so that
276
+ // we can check for that case later in OnEndElement() and skip the entire <item>
277
+ if (enclosure.IsValid ())
278
+ ctxt.enclosures .push_back (enclosure);
186
279
}
187
280
else if (strcmp (name, NODE_CRITICAL_UPDATE) == 0 )
188
281
{
189
- if (!ctxt.items .empty ())
190
- ctxt.items .back ().CriticalUpdate = true ;
282
+ ctxt.current .CriticalUpdate = true ;
191
283
}
192
284
}
193
285
}
194
286
195
287
196
- /* *
197
- * Returns true if item os is exactly "windows"
198
- * or if item is "windows-arm64" on 64bit ARM
199
- * or if item is "windows-x64" on 64bit Intel/AMD
200
- * or if item is "windows-x86" on 32bit
201
- * and is above minimum version
202
- */
203
- bool is_suitable_windows_item (const Appcast &item)
204
- {
205
- if (!is_windows_version_acceptable (item))
206
- return false ;
207
-
208
- if (item.Os == OS_MARKER)
209
- return true ;
210
-
211
- if (item.Os .compare (0 , OS_MARKER_LEN, OS_MARKER) != 0 )
212
- return false ;
213
-
214
- // Check suffix for matching bitness
215
- #ifdef _WIN64
216
- #if defined(__AARCH64EL__) || defined(_M_ARM64)
217
- return item.Os .compare (OS_MARKER_LEN, std::string::npos, " -arm64" ) == 0 ;
218
- #else
219
- return item.Os .compare (OS_MARKER_LEN, std::string::npos, " -x64" ) == 0 ;
220
- #endif // defined(__AARCH64EL__) || defined(_M_ARM64)
221
- #else
222
- return item.Os .compare (OS_MARKER_LEN, std::string::npos, " -x86" ) == 0 ;
223
- #endif // _WIN64
224
- }
225
-
226
-
227
- void trim_whitespace (std::string& s)
228
- {
229
- size_t startpos = s.find_first_not_of (" \t\r\n " );
230
- if (startpos != std::string::npos)
231
- s = s.substr (startpos);
232
- size_t endpos = s.find_last_not_of (" \t\r\n " );
233
- if (endpos != std::string::npos)
234
- s = s.substr (0 , endpos + 1 );
235
- }
236
-
237
-
238
288
void XMLCALL OnEndElement (void *data, const char *name)
239
289
{
240
290
ContextData& ctxt = *static_cast <ContextData*>(data);
@@ -276,8 +326,31 @@ void XMLCALL OnEndElement(void *data, const char *name)
276
326
else if (strcmp (name, NODE_ITEM) == 0 )
277
327
{
278
328
ctxt.in_item --;
279
- if (is_suitable_windows_item (ctxt.items [ctxt.items .size () - 1 ]))
329
+
330
+ Appcast& item = ctxt.current ;
331
+
332
+ if (!ctxt.legacy_dsa_signature .empty () && item.enclosure .DsaSignature .empty ())
333
+ item.enclosure .DsaSignature = ctxt.legacy_dsa_signature ;
334
+
335
+ if (!ctxt.enclosures .empty ())
336
+ {
337
+ item.enclosure = find_best_enclosure_for_os_arch (ctxt.enclosures );
338
+ if (!item.enclosure .IsValid ())
339
+ {
340
+ // There are enclosures (e.g. weblink is not used), but all enclosures are
341
+ // incompatible. This means the <item> is not meant for this OS and should be
342
+ // skipped (as Sparkle does; there may be another <item> for us).
343
+ return ;
344
+ }
345
+ }
346
+
347
+ if (item.IsValid () && is_compatible_with_windows_version (item))
348
+ {
349
+ ctxt.all_items .push_back (item);
350
+
351
+ // FIXME: this is premature, we should sort appcast items by date and pick the newest (as Sparkle does)
280
352
XML_StopParser (ctxt.parser , XML_TRUE);
353
+ }
281
354
}
282
355
}
283
356
else if (strcmp (name, NODE_CHANNEL) == 0 )
@@ -293,10 +366,7 @@ void XMLCALL OnEndElement(void *data, const char *name)
293
366
void XMLCALL OnText (void *data, const char *s, int len)
294
367
{
295
368
ContextData& ctxt = *static_cast <ContextData*>(data);
296
- if (ctxt.items .empty ())
297
- return ;
298
-
299
- Appcast& item = ctxt.items .back ();
369
+ Appcast& item = ctxt.current ;
300
370
301
371
if (ctxt.in_relnotes )
302
372
{
@@ -326,7 +396,7 @@ void XMLCALL OnText(void *data, const char *s, int len)
326
396
}
327
397
else if (ctxt.in_dsasignature )
328
398
{
329
- item. DsaSignature . append (s, len);
399
+ ctxt. legacy_dsa_signature . assign (s, len);
330
400
}
331
401
else if (ctxt.in_min_os_version )
332
402
{
@@ -365,25 +435,12 @@ Appcast Appcast::Load(const std::string& xml)
365
435
366
436
XML_ParserFree (p);
367
437
368
- if (ctxt.items .empty ())
438
+ if (ctxt.all_items .empty ())
369
439
return Appcast (); // invalid
370
440
371
- /*
372
- * Search for first <item> which specifies with the attribute sparkle:os set to "windows"
373
- * or "windows-x64"/"windows-arm64"/"windows-x86" based on this modules bitness and meets the minimum
374
- * os version, if set. If none, use the first item that meets the minimum os version, if set.
375
- */
376
- std::vector<Appcast>::iterator it = std::find_if (ctxt.items .begin (), ctxt.items .end (), is_suitable_windows_item);
377
- if (it != ctxt.items .end ())
378
- return *it;
379
- else
380
- {
381
- it = std::find_if (ctxt.items .begin (), ctxt.items .end (), is_windows_version_acceptable);
382
- if (it != ctxt.items .end ())
383
- return *it;
384
- else
385
- return Appcast (); // There are no items that meet the set minimum os version
386
- }
441
+ // the items were already filtered to only include those compatible with the current OS + arch
442
+ // and meeting minimum OS version requirements, so we can just return the first one
443
+ return ctxt.all_items .front ();
387
444
}
388
445
389
446
} // namespace winsparkle
0 commit comments