31
32:- module(plweb_download, []). 33:- use_module(library(http/html_write)). 34:- use_module(library(http/js_write)). 35:- use_module(library(http/http_dispatch)). 36:- use_module(library(http/http_path)). 37:- use_module(library(http/http_parameters)). 38:- use_module(library(http/http_dirindex)). 39:- use_module(library(http/http_wrapper)). 40:- use_module(library(http/http_cors)). 41:- use_module(library(dcg/basics)). 42:- use_module(library(broadcast)). 43:- use_module(library(pairs)). 44:- use_module(library(lists)). 45:- use_module(library(apply)). 46:- use_module(library(error)). 47:- use_module(library(filesex)). 48:- use_module(library(persistency)). 49:- use_module(library(crypto)). 50:- use_module(library(random)). 51:- use_module(library(debug)). 52:- use_module(wiki). 53
57
58:- http_handler(download(devel), download_table, []). 59:- http_handler(download(stable), download_table, []). 60:- http_handler(download(old), download_table, []). 61:- http_handler(download('daily/bin/'), download_daily, []). 62:- http_handler(download(.), download,
63 [prefix, spawn(download), priority(10)]). 64:- http_handler(root(download), http_redirect(moved, download(.)),
65 [priority(10)]). 66
71
72download_table(Request) :-
73 http_parameters(Request,
74 [ show(Show, [oneof([all,latest]), default(latest)])
75 ]),
76 memberchk(path(Path), Request),
77 http_absolute_location(root(download), DownLoadRoot, []),
78 atom_concat(DownLoadRoot, DownLoadDir, Path),
79 absolute_file_name(download(DownLoadDir),
80 Dir,
81 [ file_type(directory),
82 access(read)
83 ]),
84 list_downloads(Dir, [show(Show), request(Request)]).
85
87
88list_downloads(Dir, Options) :-
89 ( wiki_file_to_dom(Dir, 'header.txt', Header0)
90 -> ( Header0 = [h1(_, Title)|Header]
91 -> true
92 ; Header = Header0
93 )
94 ; Header = []
95 ),
96 ( var(Title)
97 -> Title = 'SWI-Prolog downloads'
98 ; true
99 ),
100 reply_html_page(
101 download(Dir, Title),
102 title('SWI-Prolog downloads'),
103 [ \html(Header),
104 br(clear(all)),
105 table(class(downloads),
106 \download_table(Dir, Options)),
107 \machine_download_text,
108 \wiki(Dir, 'footer.txt')
109 ]).
110
111wiki_file_to_dom(Dir, File, DOM) :-
112 directory_file_path(Dir, File, WikiFile),
113 access_file(WikiFile, read), !,
114 wiki_file_to_dom(WikiFile, DOM).
115
116wiki(Dir, File) -->
117 { wiki_file_to_dom(Dir, File, DOM) }, !,
118 html(DOM).
119wiki(_, _) -->
120 [].
121
122machine_download_text -->
123 html({|html||
124<div class="machine-download">
125Install scripts may download the SHA256 checksum by appending
126<code>.sha256</code> to the file name. Scripts can download
127the latest version by replacing the version of the file with
128<code>latest</code>. This causes the server to reply with the
129location of the latest version using an
130<code>HTTP 303 See Other</code> message.
131</div>
132 |}).
133
134
135download_table(Dir, Options) -->
136 list_files(Dir, bin, bin, 'Binaries', Options),
137 list_files(Dir, src, src, 'Sources', Options),
138 list_files(Dir, doc, doc, 'Documentation', Options),
139 toggle_show(Options).
140
145
146toggle_show(Options) -->
147 { option(request(Request), Options),
148 memberchk(path(Path), Request), !,
149 file_base_name(Path, MySelf),
150 ( option(show(all), Options)
151 -> NewShow = latest
152 ; NewShow = all
153 )
154 },
155 html(tr(td([class(toggle), colspan(3)],
156 a(href(MySelf+'?show='+NewShow),
157 [ 'Show ', NewShow, ' files' ])))).
158toggle_show(_) -->
159 [].
160
169
170list_files(Dir, SubDir, Class, Label, Options) -->
171 { directory_file_path(Dir, SubDir, Directory),
172 download_files(Directory, Class, Files, Options),
173 Files \== []
174 },
175 html(tr(th(colspan(3), Label))),
176 list_files(Files).
177list_files(_, _, _, _, _) -->
178 [].
179
180list_files([]) --> [].
181list_files([H|T]) -->
182 list_file(H),
183 list_files(T).
184
185list_file(File) -->
186 html(tr(class(download),
187 [ td(class(dl_icon), \file_icon(File)),
188 td(class(dl_size), \file_size(File)),
189 td(class(dl_file), \file_description(File))
190 ])).
191
192file_icon(file(Type, PlatForm, _, _, _)) -->
193 { icon_for_file(Type, PlatForm, Icon, Alt), !,
194 http_absolute_location(icons(Icon), HREF, [])
195 },
196 html(img([src(HREF), alt(Alt)])).
197file_icon(_) -->
198 html(?). 199
200icon_for_file(bin, linux(universal),
201 'linux.png', 'Linux 32/64 intel').
202icon_for_file(bin, linux(_,_),
203 'linux32.gif', 'Linux RPM').
204icon_for_file(bin, macos(lion,_),
205 'lion.png', 'Lion').
206icon_for_file(bin, macos(snow_leopard,_),
207 'snowleopard.gif', 'Snow Leopard').
208icon_for_file(bin, macos(snow_leopard_and_later,_),
209 'macapp.png', 'Snow Leopard and later').
210icon_for_file(bin, macos(bundle,_),
211 'macapp.png', 'MacOS bundle').
212icon_for_file(bin, macos(_,_),
213 'mac.gif', 'MacOSX version').
214icon_for_file(_, windows(win32),
215 'win32.gif', 'Windows version (32-bits)').
216icon_for_file(_, windows(win64),
217 'win64.gif', 'Windows version (64-bits)').
218icon_for_file(src, _,
219 'src.gif', 'Source archive').
220icon_for_file(_, pdf,
221 'pdf.gif', 'PDF file').
222
223
224file_size(file(_, _, _, _, Path)) -->
225 { size_file(Path, Bytes)
226 },
227 html('~D bytes'-[Bytes]).
228
229file_description(file(bin, PlatForm, Version, _, Path)) -->
230 { down_file_href(Path, HREF)
231 },
232 html([ a(href(HREF),
233 [ 'SWI-Prolog ', \version(Version), ' for ',
234 \platform(PlatForm)
235 ]),
236 \platform_notes(PlatForm, Path),
237 \checksum(Path)
238 ]).
239file_description(file(src, Format, Version, _, Path)) -->
240 { down_file_href(Path, HREF)
241 },
242 html([ a(href(HREF),
243 [ 'SWI-Prolog source for ', \version(Version)
244 ]),
245 \platform_notes(Format, Path),
246 \checksum(Path)
247 ]).
248file_description(file(doc, Format, Version, _, Path)) -->
249 { down_file_href(Path, HREF)
250 },
251 html([ a(href(HREF),
252 [ 'SWI-Prolog ', \version(Version),
253 ' reference manual in PDF'
254 ]),
255 \platform_notes(Format, Path)
256 ]).
257file_description(file(pkg(Pkg), PlatForm, Version, _, Path)) -->
258 { down_file_href(Path, HREF)
259 },
260 html([ a(href(HREF),
261 [ \package(Pkg), ' (version ', \version(Version), ') for ',
262 \platform(PlatForm)
263 ]),
264 \platform_notes(pkg(Pkg), Path)
265 ]).
266
267package(Name) -->
268 html([ 'Package ', Name ]).
269
270version(version(Major, Minor, Patch, '')) --> !,
271 html(b('~w.~w.~w'-[Major, Minor, Patch])).
272version(version(Major, Minor, Patch, Tag)) -->
273 html(b('~w.~w.~w-~w'-[Major, Minor, Patch, Tag])).
274
275checksum(Path) -->
276 { file_checksum(Path, SHA256) },
277 html(div([ class(checksum),
278 title('You can use the checksum to verify the integrity \c
279 of the downloaded file. It provides some protection \c
280 against deliberate tamporing with the file.')
281 ],
282 [ span(class('checkum-header'), 'SHA256'), :,
283 span(class([checksum,sha256]), SHA256)
284 ])).
285
286down_file_href(Path, HREF) :-
287 absolute_file_name(download(.),
288 Dir,
289 [ file_type(directory),
290 access(read)
291 ]),
292 atom_concat(Dir, SlashLocal, Path),
293 delete_leading_slash(SlashLocal, Local),
294 add_envelope(Local, SafeLocal),
295 http_absolute_location(download(SafeLocal), HREF, []).
296
297delete_leading_slash(SlashPath, Path) :-
298 atom_concat(/, Path, SlashPath), !.
299delete_leading_slash(Path, Path).
300
301platform(linux(universal)) -->
302 html(['Linux 32/64 bits (TAR)']).
303platform(linux(rpm, _)) -->
304 html(['i586/Linux (RPM)']).
305platform(macos(Name, CPU)) -->
306 html(['MacOSX ', \html_macos_version(Name, CPU), ' on ', \cpu(CPU)]).
307platform(windows(win32)) -->
308 html(['Microsoft Windows (32 bit)']).
309platform(windows(win64)) -->
310 html(['Microsoft Windows (64 bit)']).
311
312html_macos_version(tiger, _) --> html('10.4 (Tiger)').
313html_macos_version(leopard, _) --> html('10.5 (Leopard)').
314html_macos_version(snow_leopard, _) --> html('10.6 (Snow Leopard)').
315html_macos_version(lion, _) --> html('10.7 (Lion)').
316html_macos_version(snow_leopard_and_later, _)
317 --> html('10.6 (Snow Leopard) and later').
318html_macos_version(bundle, x86_64) --> html('10.12 (Sierra) and later').
319html_macos_version(bundle, fat) --> html('10.14 (Mojave) and later').
320html_macos_version(OS, _CPU) --> html(OS).
321
322cpu(fat) --> !, html("x86_64 and arm64").
323cpu(CPU) --> html(CPU).
324
325
330
331platform_notes(Platform, Path) -->
332 { file_directory_name(Path, Dir),
333 platform_note_file(Platform, File),
334 atomic_list_concat([Dir, /, File], NoteFile),
335 debug(download, 'Trying note-file ~q', [NoteFile]),
336 access_file(NoteFile, read), !,
337 debug(download, 'Found note-file ~q', [NoteFile]),
338 wiki_file_to_dom(NoteFile, DOM)
339 },
340 html(DOM).
341platform_notes(_, _) -->
342 [].
343
344platform_note_file(linux(rpm,_), 'linux-rpm.txt').
345platform_note_file(linux(universal), 'linux.txt').
346platform_note_file(windows(win32), 'win32.txt').
347platform_note_file(windows(win64), 'win64.txt').
348platform_note_file(pkg(Pkg), File) :-
349 file_name_extension(Pkg, txt, File).
350platform_note_file(macos(Version,fat), File) :-
351 atomic_list_concat([macosx, -, fat, -, Version, '.txt'], File).
352platform_note_file(macos(Version,_), File) :-
353 atomic_list_concat([macosx, -, Version, '.txt'], File).
354platform_note_file(macos(_,_), 'macosx.txt').
355platform_note_file(tgz, 'src-tgz.txt').
356platform_note_file(pdf, 'doc-pdf.txt').
357
358
359 362
367
368:- dynamic
369 download_cache/6. 370
371download_files(Dir, Class, Files, Options0) :-
372 exists_directory(Dir), !,
373 include(download_option, Options0, Options),
374 term_hash(ci(Dir,Class,Options), Hash),
375 time_file(Dir, DirTime),
376 ( download_cache(Hash, Dir, Class, Options, Time, Files0),
377 ( DirTime == Time
378 -> true
379 ; retractall(download_cache(Hash, Dir, Class, Options, _, _)),
380 fail
381 )
382 -> true
383 ; download_files_nc(Dir, Class, Files0, Options),
384 asserta(download_cache(Hash, Dir, Class, Options, DirTime, Files0))
385 ),
386 Files = Files0.
387download_files(_, _, [], _).
388
389clear_download_cache :-
390 retractall(download_cache(_Hash, _Dir, _Class, _Options, _Time, _Files0)).
391
392download_option(show(_)).
393
394
395download_files_nc(Directory, Class, Sorted, Options) :-
396 atom_concat(Directory, '/*', Pattern),
397 expand_file_name(Pattern, Files),
398 classify_files(Files, Class, Classified, Options),
399 sort_files(Classified, Sorted, Options).
400
401classify_files([], _, [], _).
402classify_files([H0|T0], Class, [H|T], Options) :-
403 classify_file(H0, H, Options),
404 arg(1, H, Classification),
405 subsumes_term(Class, Classification), !,
406 classify_files(T0, Class, T, Options).
407classify_files([_|T0], Class, T, Options) :-
408 classify_files(T0, Class, T, Options).
409
411
412classify_file(Path, file(Type, Platform, Version, Name, Path), Options) :-
413 file_base_name(Path, Name),
414 atom_codes(Name, Codes),
415 phrase(file(Type, Platform, Version, Options), Codes).
416
417file(bin, macos(OSVersion, CPU), Version, Options) -->
418 { option(show(all), Options) },
419 "swi-prolog-", opt_devel, long_version(Version), "-",
420 macos_version(OSVersion),
421 ( "-",
422 macos_cpu(CPU)
423 -> ""
424 ; { macos_def_cpu(OSVersion, CPU) }
425 ),
426 ".mpkg.zip", !.
428file(bin, macos(bundle, intel), Version, _) -->
429 "swipl-", long_version(Version), opt_release(_),
430 opt_cpu(_),
431 ".dmg", !.
432file(bin, macos(bundle, fat), Version, _) -->
433 "swipl-", long_version(Version), opt_release(_),
434 ".fat.dmg", !.
435file(bin, macos(snow_leopard_and_later, intel), Version, _) -->
436 "SWI-Prolog-", long_version(Version),
437 ".dmg", !.
438file(bin, windows(WinType), Version, _) -->
439 "swipl-", long_version(Version), opt_release(_),
440 cmake_win_type(WinType),
441 ".exe", !.
442file(bin, windows(WinType), Version, _) -->
443 win_type(WinType), "pl",
444 short_version(Version),
445 ".exe", !.
446file(bin, windows(WinType), Version, _) -->
447 swipl, win_type(WinType), "-",
448 short_version(Version),
449 ".exe", !.
450file(bin, linux(rpm, suse), Version, _) -->
451 swipl, long_version(Version), "-", digits(_Build), ".i586.rpm", !.
452file(bin, linux(universal), Version, _) -->
453 "swipl-",
454 long_version(Version), "-", "linux",
455 ".tar.gz", !.
456file(src, tgz, Version, _) -->
457 swipl, long_version(Version), ".tar.gz", !.
458file(doc, pdf, Version, _) -->
459 "SWI-Prolog-", long_version(Version), ".pdf", !.
460
461swipl --> "swipl-", !.
462swipl --> "pl-".
463
464opt_release(Rel) --> "-", int(Rel, 4), !.
465opt_release(-) --> "".
466
467opt_devel --> "devel-", !.
468opt_devel --> "".
469
470opt_cpu(x86_64) --> ".", "x86_64", !.
471opt_cpu(unknown) --> "".
472
473macos_version(tiger) --> "tiger".
474macos_version(leopard) --> "leopard".
475macos_version(snow_leopard) --> "snow-leopard".
476macos_version(lion) --> "lion".
477
478macos_cpu(ppc) --> "powerpc".
479macos_cpu(intel) --> "intel".
480macos_cpu(x86) --> "32bit".
481
482macos_def_cpu(snow_leopard, intel) :- !.
483macos_def_cpu(lion, intel) :- !.
484macos_def_cpu(_, ppc).
485
486win_type(win32) --> "w32".
487win_type(win64) --> "w64".
488
489cmake_win_type(win64) --> ".", "x64".
490cmake_win_type(win32) --> ".", "x86".
491
492long_version(version(Major, Minor, Patch, Tag)) -->
493 int(Major, 1), ".", int(Minor, 2), ".", int(Patch, 2), !,
494 tag(Tag), !.
495long_version(latest) -->
496 "latest".
497
498tag(Tag) -->
499 "-", alnums(Codes), !,
500 { atom_codes(Tag, Codes) }.
501tag('') --> "".
502
503int(Value, MaxDigits) -->
504 digits(Digits),
505 { length(Digits, Len),
506 Len =< MaxDigits,
507 Len > 0,
508 number_codes(Value, Digits)
509 }.
510
511alnums([H|T]) -->
512 [H], { code_type(H, alnum) }, !,
513 alnums(T).
514alnums([]) --> "".
515
516short_version(version(Major, Minor, Patch, Tag)) -->
517 digits(Digits),
518 { Digits = [D1,D2,D3]
519 -> number_codes(Major, [D1]),
520 number_codes(Minor, [D2]),
521 number_codes(Patch, [D3])
522 ; Digits = [D1,D2,D3,D4]
523 -> ( number_codes(51, [D1,D2]) 524 -> number_codes(Major, [D1]),
525 number_codes(Minor, [D2,D3]),
526 number_codes(Patch, [D4])
527 ; number_codes(Major, [D1]),
528 number_codes(Minor, [D2]),
529 number_codes(Patch, [D3,D4])
530 )
531 ; Digits = [D1,D2,D3,D4,D5]
532 -> number_codes(Major, [D1]),
533 number_codes(Minor, [D2,D3]),
534 number_codes(Patch, [D4,D5])
535 },
536 tag(Tag), !.
537short_version(latest) -->
538 "latest".
539
549
550sort_files(In, Out, Options) :-
551 map_list_to_pairs(map_type, In, Typed0),
552 ( option(show(all), Options)
553 -> Typed = Typed0
554 ; exclude(old_tagged_file, Typed0, Typed)
555 ),
556 keysort(Typed, TSorted),
557 group_pairs_by_key(TSorted, TGrouped),
558 maplist(sort_group_by_version, TGrouped, TGroupSorted),
559 ( option(show(all), Options)
560 -> pairs_values(TGroupSorted, TValues),
561 flatten(TValues, Out)
562 ; take_latest(TGroupSorted, Out)
563 ).
564
565map_type(File, Tag) :-
566 File = file(Type, Platform, _Version, _Name, _Path),
567 type_tag(Type, Platform, Tag).
568
569type_tag(bin, linux(A), tag(10, linux(A))) :- !.
570type_tag(bin, linux(A,B), tag(11, linux(A,B))) :- !.
571type_tag(bin, windows(A), tag(Tg, windows(A))) :- !,
572 win_tag(A, Tg2),
573 Tg is 20+Tg2.
574type_tag(bin, macos(A,B), tag(Tg, macos(A,B))) :- !,
575 mac_tag(A, Tg2),
576 Tg is 30+Tg2.
577type_tag(src, Format, tag(40, Format)) :- !.
578type_tag(doc, Format, tag(50, Format)) :- !.
579type_tag(X, Y, tag(60, X-Y)).
580
581mac_tag(bundle, 4).
582mac_tag(snow_leopard_and_later, 5).
583mac_tag(lion, 6).
584mac_tag(snow_leopard, 7).
585mac_tag(leopard, 8).
586mac_tag(tiger, 9).
587
588win_tag(win64, 1).
589win_tag(win32, 2).
590
591sort_group_by_version(Tag-Files, Tag-Sorted) :-
592 map_list_to_pairs(tag_version, Files, TFiles),
593 keysort(TFiles, TRevSorted),
594 pairs_values(TRevSorted, RevSorted),
595 reverse(RevSorted, Sorted).
596
597tag_version(File, Tag) :-
598 File = file(_,_,Version,_,_),
599 version_tag(Version, Tag).
600
601version_tag(version(Major, Minor, Patch, Tag),
602 version(Major, Minor, Patch, Order)) :-
603 ( pre_version(Tag, Order)
604 -> true
605 ; print_message(error,
606 error(domain_error(pre_release_version, Tag),_)),
607 Order = pre(-100, 0)
608 ).
609
610pre_version('', pre(0, 0)) :- !.
611pre_version(NrA, pre(0, 0)) :-
612 atom_number(NrA, _Nr), !.
613pre_version(Tag, pre(TagOrder, N)) :-
614 tag(TagPrefix, TagOrder),
615 atom_concat(TagPrefix, NA, Tag),
616 atom_number(NA, N).
617
618tag(rc, -1).
619tag(beta, -2).
620tag(alpha, -3).
621
622take_latest([], []).
623take_latest([_-[H|_]|T0], [H|T]) :- !,
624 take_latest(T0, T).
625take_latest([_-[]|T0], T) :- !, 626 take_latest(T0, T).
627
629
630old_tagged_file(tag(_,Type)-_File) :-
631 old_file_type(Type).
632
633old_file_type(linux(_)).
634old_file_type(linux(_,_)).
635old_file_type(macos(_,ppc)).
636old_file_type(macos(tiger,_)).
637old_file_type(macos(snow_leopard_and_later,_)).
638
639
640 643
652
653download(Request) :-
654 memberchk(path_info(Download), Request),
655 file_name_extension(File, envelope, Download), !,
656 envelope(File).
657download(Request) :-
658 memberchk(path_info(Download), Request),
659 ( file_name_extension(File, sha256, Download)
660 -> true
661 ; File = Download
662 ),
663 download_file(File, AbsFile),
664 cors_enable,
665 format('Cross-origin-resource-policy: cross-origin\n'),
666 ( File == Download
667 -> http_peer(Request, Remote),
668 broadcast(download(File, Remote)),
669 http_reply_file(AbsFile, [unsafe(true)], Request)
670 ; file_checksum(AbsFile, SHA256),
671 format('Content-type: text/plain~n~n'),
672 format('~w~n', [SHA256])
673 ).
674download(Request) :-
675 memberchk(path_info(Download), Request),
676 classify_file(Download, file(Class,Platform,latest,_,_), [show(last)]),
677 file_directory_name(Download, Dir),
678 absolute_file_name(download(Dir),
679 AbsDir,
680 [ access(read),
681 file_type(directory),
682 file_errors(fail)
683 ]),
684 download_files(AbsDir, Class, Files, [show(last)]),
685 memberchk(file(Class, Platform, _, File, _), Files), !,
686 directory_file_path(Dir, File, Redirect),
687 http_link_to_id(download, path_postfix(Redirect), URI),
688 http_redirect(see_other, URI, Request).
689download(Request) :-
690 ( memberchk(path_info(Download), Request)
691 -> http_safe_file(download(Download), [])
692 ; Download = '.'
693 ),
694 absolute_file_name(download(Download),
695 AbsFile,
696 [ access(read),
697 file_errors(fail),
698 file_type(directory)
699 ]), !,
700 http_reply_dirindex(AbsFile,
701 [ unsafe(true),
702 name(name_cell)
703 ], Request).
704download(Request) :-
705 memberchk(path(Path), Request),
706 existence_error(http_location, Path).
707
708download_file(File, AbsFile) :-
709 http_safe_file(download(File), []),
710 absolute_file_name(download(File),
711 AbsFile,
712 [ access(read),
713 file_errors(fail)
714 ]).
715
716:- public
717 name_cell//1. 718
719name_cell(File) -->
720 { needs_envelope(File),
721 file_base_name(File, Name),
722 uri_encoded(path, Name, Ref0),
723 file_name_extension(Ref0, envelope, Ref)
724 },
725 html(a(href(Ref), Name)).
726name_cell(File) -->
727 { file_base_name(File, Name),
728 uri_encoded(path, Name, Ref)
729 },
730 html(a(href(Ref), Name)).
731
735
736download_daily(_Request) :-
737 absolute_file_name(download('daily/bin'), Dir,
738 [ file_type(directory),
739 access(read)
740 ]),
741 reply_html_page(
742 download(Dir, 'Download daily builds for Windows'),
743 title('Download daily builds for Windows'),
744 [ \explain_win_daily,
745 \directory_index(Dir,
746 [ order_by(time),
747 order(descending),
748 name(name_cell)
749 ])
750 ]).
751
752
753explain_win_daily -->
754 html({|html||
755 <p>The table below provides access to the most recent 7
756 daily builds of SWI-Prolog for Windows, both the 32- and
757 64-bit versions. The build is done automatically from the
758 <a href="/git/">GIT sources</a>. The files use the following
759 naming convention:
760 </p>
761 <ul>
762 <li><code>swipl-w</code><var>bits</var><code>-</code><var>date</var><code>.exe</code>
763 </ul>
764 <p>
765 Please note that these versions <b>may be unstable!</b> It is
766 adviced to follow current discussions on the
767 <a href="/Mailinglist.html">mailing
768 list</a> and/or the git commit messages at
769 <a href="https://github.com/SWI-Prolog/swipl-devel">GitHub</a>.
770 The primary purpose of the daily builds is to quickly provide
771 binaries after a bug report.
772 </p>
773 |}).
774
775
776 779
780needs_envelope(File) :-
781 file_name_extension(_, exe, File).
782
783add_envelope(File, Envelope) :-
784 needs_envelope(File),
785 !,
786 file_name_extension(File, envelope, Envelope).
787add_envelope(File, File).
788
789envelope(File) :-
790 maybe(0.1),
791 download_file(File, AbsFile),
792 file_checksum(AbsFile, OkHash),
793 compute_file_checksum(AbsFile, NewHash),
794 NewHash \== OkHash,
795 !,
796 reply_html_page(
797 download(File, 'Possibly tampered binary'),
798 title('Possibly tampered binary'),
799 \tampered(File, OkHash, NewHash)).
800envelope(File) :-
801 file_base_name(File, Base),
802 reply_html_page(
803 download(Base, 'Download binary'),
804 title('Download a binary file'),
805 \envelope(File)).
806
807envelope(File) -->
808 { http_absolute_location(icons('alert.gif'), Alert, []),
809 http_absolute_location(icons('vt_logo.png'), VTLogo, []),
810 download_file(File, AbsFile),
811 file_checksum(AbsFile, Hash),
812 file_base_name(File, Base),
813 format(atom(VTHREF), 'https://www.virustotal.com/file/~w/analysis/', Hash)
814 },
815 html({|html(Base, Hash, VTHREF, VTLogo, Alert)||
816<p><img src=Alert style="float:left">
817Windows antivirus software works using <i>signatures</i> and <i>heuristics</i>.
818Using the huge amount of virusses and malware known today, arbitrary executables
819are often <a href="https://en.wikipedia.org/wiki/Antivirus_software#Problems_caused_by_false_positives">falsily classified as malicious</a>.
820<a href="https://safebrowsing.google.com/">Google Safe Browsing</a>, used by
821most modern browsers, therefore often classifies our Windows binaries as
822malware. You can use e.g., <a href="https://www.virustotal.com/gui/home/url">virustotal</a> to verify files with a large number of antivirus programs.
823</p>
824
825<p>
826Our Windows binaries are cross-compiled on an isolated Linux container. The
827integrity of the binaries on the server is regularly verified by validating its
828SHA256 fingerprint.
829</p>
830
831<p>
832Please select the checkbox below to enable the actual download link.
833</p>
834
835<table>
836<tr><td><input type="checkbox" id="understand"><td>I understand</tr>
837<tr><td><td><a id="download">Download <code>Base</code></a>
838<span style="color:#888; font-size:small;">(SHA256: <code>Hash</code>)</span></tr>
839<tr><td style="text-align:right"><img src=VTLogo style="width:1.5ex"><td><a href=VTHREF>VIRUSTOTAL Scan Result</a></tr>
840</table>
841 |}),
842 js_script({|javascript(Base)||
843$(function() {
844 $("#understand").prop("checked", false)
845 .on("click", function() {
846 $("#download").attr("href", Base);
847 });
848});
849
850 |}).
851
852tampered(File, OkHash, NewHash) -->
853 { http_absolute_location(icons('alert.gif'), Alert, [])
854 },
855 html({|html(File, Alert, OkHash, NewHash)||
856<p><img src=Alert style="float:left">
857The file <code>File</code> SHA256 signature has changed. Please
858report this at <a href="mailto:bugs@swi-prolog.org">bugs@swi-prolog.org</a>
859 |}).
860
861
862 865
866:- persistent
867 sha256(path:atom,
868 sha256:atom). 869
870checksum_file(File) :-
871 absolute_file_name(data('checksum.db'), File,
872 [ access(write) ]).
873
874attach_db :-
875 checksum_file(File),
876 ( db_attached(File)
877 -> true
878 ; db_attach(File, [])
879 ).
880
891
892file_checksum(Path, Sum) :-
893 attach_db,
894 sha256(Path, Sum0), !,
895 Sum = Sum0.
896file_checksum(Path, Sum) :-
897 compute_file_checksum(Path, Sum).
898
899compute_file_checksum(Path, Sum) :-
900 crypto_file_hash(Path, Sum,
901 [ encoding(octet),
902 algorithm(sha256)
903 ]),
904 assert_sha256(Path, Sum)