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(installer,_),
213 'macos-pkg.png', 'MacOS installer').
214icon_for_file(bin, macos(_,_),
215 'mac.gif', 'MacOSX version').
216icon_for_file(_, windows(win32),
217 'win32.gif', 'Windows version (32-bits)').
218icon_for_file(_, windows(win64),
219 'win-x64.svg', 'Windows version (64-bits, Intel/AMD)').
220icon_for_file(_, windows(arm64),
221 'win-arm64.svg', 'Windows version (64-bits, ARM)').
222icon_for_file(src, _,
223 'src.gif', 'Source archive').
224icon_for_file(_, pdf(_),
225 'pdf.svg', 'PDF file').
226
227
228file_size(file(_, _, _, _, Path)) -->
229 { size_file(Path, Bytes)
230 },
231 html('~D bytes'-[Bytes]).
232
233file_description(file(bin, PlatForm, Version, _, Path)) -->
234 { down_file_href(Path, HREF)
235 },
236 html([ a(href(HREF),
237 [ 'SWI-Prolog ', \version(Version), ' for ',
238 \platform(PlatForm)
239 ]),
240 \platform_notes(PlatForm, Path),
241 \checksum(Path)
242 ]).
243file_description(file(src, Format, Version, _, Path)) -->
244 { down_file_href(Path, HREF)
245 },
246 html([ a(href(HREF),
247 [ 'SWI-Prolog source for ', \version(Version)
248 ]),
249 \platform_notes(Format, Path),
250 \checksum(Path)
251 ]).
252file_description(file(doc, pdf(swipl), Version, _, Path)) -->
253 { down_file_href(Path, HREF)
254 },
255 html([ a(href(HREF),
256 [ 'SWI-Prolog ', \version(Version),
257 ' reference manual in PDF'
258 ]),
259 \platform_notes(pdf(swipl), Path)
260 ]).
261file_description(file(doc, pdf(xpce), Version, _, Path)) -->
262 { down_file_href(Path, HREF)
263 },
264 html([ a(href(HREF),
265 [ 'XPCE User guide ', \version(Version), ' in PDF'
266 ]),
267 \platform_notes(pdf(xpce), Path)
268 ]).
269file_description(file(pkg(Pkg), PlatForm, Version, _, Path)) -->
270 { down_file_href(Path, HREF)
271 },
272 html([ a(href(HREF),
273 [ \package(Pkg), ' (version ', \version(Version), ') for ',
274 \platform(PlatForm)
275 ]),
276 \platform_notes(pkg(Pkg), Path)
277 ]).
278
279package(Name) -->
280 html([ 'Package ', Name ]).
281
282version(version(Major, Minor, Patch, '')) --> !,
283 html(b('~w.~w.~w'-[Major, Minor, Patch])).
284version(version(Major, Minor, Patch, Tag)) -->
285 html(b('~w.~w.~w-~w'-[Major, Minor, Patch, Tag])).
286
287checksum(Path) -->
288 { file_checksum(Path, SHA256) },
289 html(div([ class(checksum),
290 title('You can use the checksum to verify the integrity \c
291 of the downloaded file. It provides some protection \c
292 against deliberate tamporing with the file.')
293 ],
294 [ span(class('checkum-header'), 'SHA256'), :,
295 span(class([checksum,sha256]), SHA256)
296 ])).
297
298down_file_href(Path, HREF) :-
299 absolute_file_name(download(.),
300 Dir,
301 [ file_type(directory),
302 access(read)
303 ]),
304 atom_concat(Dir, SlashLocal, Path),
305 delete_leading_slash(SlashLocal, Local),
306 add_envelope(Local, SafeLocal),
307 http_absolute_location(download(SafeLocal), HREF, []).
308
309delete_leading_slash(SlashPath, Path) :-
310 atom_concat(/, Path, SlashPath), !.
311delete_leading_slash(Path, Path).
312
313platform(linux(universal)) -->
314 html(['Linux 32/64 bits (TAR)']).
315platform(linux(rpm, _)) -->
316 html(['i586/Linux (RPM)']).
317platform(macos(Name, CPU)) -->
318 html(['MacOSX ', \html_macos_version(Name, CPU), ' on ', \cpu(CPU)]).
319platform(windows(win32)) -->
320 html(['Microsoft Windows (32 bit)']).
321platform(windows(win64)) -->
322 html(['Microsoft Windows (64 bit)']).
323platform(windows(arm64)) -->
324 html(['Microsoft Windows on ARM (64 bit)']).
325
326html_macos_version(tiger, _) --> html('10.4 (Tiger)').
327html_macos_version(leopard, _) --> html('10.5 (Leopard)').
328html_macos_version(snow_leopard, _) --> html('10.6 (Snow Leopard)').
329html_macos_version(lion, _) --> html('10.7 (Lion)').
330html_macos_version(snow_leopard_and_later, _)
331 --> html('10.6 (Snow Leopard) and later').
332html_macos_version(bundle, x86_64) --> html('10.12 (Sierra) and later').
333html_macos_version(bundle, fat) --> html('10.15 (Catalina) and later').
334html_macos_version(installer, _) --> html('10.15 (Catalina) and later').
335html_macos_version(OS, _CPU) --> html(OS).
336
337cpu(fat) --> !, html("x86_64 and arm64").
338cpu(CPU) --> html(CPU).
339
340
345
346platform_notes(Platform, Path) -->
347 { file_directory_name(Path, Dir),
348 platform_note_file(Platform, File),
349 atomic_list_concat([Dir, /, File], NoteFile),
350 debug(download, 'Trying note-file ~q', [NoteFile]),
351 access_file(NoteFile, read), !,
352 debug(download, 'Found note-file ~q', [NoteFile]),
353 wiki_file_to_dom(NoteFile, DOM)
354 },
355 html(DOM).
356platform_notes(_, _) -->
357 [].
358
359platform_note_file(linux(rpm,_), 'linux-rpm.txt').
360platform_note_file(linux(universal), 'linux.txt').
361platform_note_file(windows(win32), 'win32.txt').
362platform_note_file(windows(win64), 'win64.txt').
363platform_note_file(windows(arm64), 'winarm64.txt').
364platform_note_file(pkg(Pkg), File) :-
365 file_name_extension(Pkg, txt, File).
366platform_note_file(macos(Version,fat), File) :-
367 atomic_list_concat([macosx, -, fat, -, Version, '.txt'], File).
368platform_note_file(macos(Version,_), File) :-
369 atomic_list_concat([macosx, -, Version, '.txt'], File).
370platform_note_file(macos(_,_), 'macosx.txt').
371platform_note_file(tgz, 'src-tgz.txt').
372platform_note_file(pdf(swipl), 'doc-pdf.txt').
373platform_note_file(pdf(xpce), 'xpce-pdf.txt').
374
375
376 379
384
385:- dynamic
386 download_cache/6. 387
388download_files(Dir, Class, Files, Options0) :-
389 exists_directory(Dir), !,
390 include(download_option, Options0, Options),
391 term_hash(ci(Dir,Class,Options), Hash),
392 time_file(Dir, DirTime),
393 ( download_cache(Hash, Dir, Class, Options, Time, Files0),
394 ( DirTime == Time
395 -> true
396 ; retractall(download_cache(Hash, Dir, Class, Options, _, _)),
397 fail
398 )
399 -> true
400 ; download_files_nc(Dir, Class, Files0, Options),
401 asserta(download_cache(Hash, Dir, Class, Options, DirTime, Files0))
402 ),
403 Files = Files0.
404download_files(_, _, [], _).
405
406clear_download_cache :-
407 retractall(download_cache(_Hash, _Dir, _Class, _Options, _Time, _Files0)).
408
409download_option(show(_)).
410
411
412download_files_nc(Directory, Class, Sorted, Options) :-
413 atom_concat(Directory, '/*', Pattern),
414 expand_file_name(Pattern, Files),
415 classify_files(Files, Class, Classified, Options),
416 sort_files(Classified, Sorted, Options).
417
418classify_files([], _, [], _).
419classify_files([H0|T0], Class, [H|T], Options) :-
420 classify_file(H0, H, Options),
421 arg(1, H, Classification),
422 subsumes_term(Class, Classification), !,
423 classify_files(T0, Class, T, Options).
424classify_files([_|T0], Class, T, Options) :-
425 classify_files(T0, Class, T, Options).
426
428
429classify_file(Path, file(Type, Platform, Version, Name, Path), Options) :-
430 file_base_name(Path, Name),
431 atom_codes(Name, Codes),
432 phrase(file(Type, Platform, Version, Options), Codes).
433
434file(bin, macos(OSVersion, CPU), Version, Options) -->
435 { option(show(all), Options) },
436 "swi-prolog-", opt_devel, long_version(Version), "-",
437 macos_version(OSVersion),
438 ( "-",
439 macos_cpu(CPU)
440 -> ""
441 ; { macos_def_cpu(OSVersion, CPU) }
442 ),
443 ".mpkg.zip", !.
445file(bin, macos(bundle, intel), Version, _) -->
446 "swipl-", long_version(Version), opt_release(_),
447 opt_cpu(_),
448 ".dmg", !.
449file(bin, macos(bundle, fat), Version, _) -->
450 "swipl-", long_version(Version), opt_release(_),
451 ".fat.dmg", !.
452file(bin, macos(installer, fat), Version, _) -->
453 "swipl-", long_version(Version), opt_release(_),
454 ".fat.pkg", !.
455file(bin, macos(installer, arm64), Version, _) -->
456 "swipl-", long_version(Version), opt_release(_),
457 ".arm64.pkg", !.
458file(bin, macos(installer, fat), Version, _) -->
459 "swipl-", long_version(Version), opt_release(_),
460 "-fat.pkg", !.
461file(bin, macos(installer, arm64), Version, _) -->
462 "swipl-", long_version(Version), opt_release(_),
463 "-arm64.pkg", !.
464file(bin, macos(snow_leopard_and_later, intel), Version, _) -->
465 "SWI-Prolog-", long_version(Version),
466 ".dmg", !.
467file(bin, windows(WinType), Version, _) -->
468 "swipl-", long_version(Version), opt_release(_),
469 cmake_win_type(WinType),
470 ".exe", !.
471file(bin, windows(WinType), Version, _) -->
472 win_type(WinType), "pl",
473 short_version(Version),
474 ".exe", !.
475file(bin, windows(WinType), Version, _) -->
476 swipl, win_type(WinType), "-",
477 short_version(Version),
478 ".exe", !.
479file(bin, linux(rpm, suse), Version, _) -->
480 swipl, long_version(Version), "-", digits(_Build), ".i586.rpm", !.
481file(bin, linux(universal), Version, _) -->
482 "swipl-",
483 long_version(Version), "-", "linux",
484 ".tar.gz", !.
485file(src, tgz, Version, _) -->
486 swipl, long_version(Version), ".tar.gz", !.
487file(doc, pdf(swipl), Version, _) -->
488 "SWI-Prolog-", long_version(Version), ".pdf", !.
489file(doc, pdf(xpce), Version, _) -->
490 "XPCE-userguide-", long_version(Version), ".pdf", !.
491
492swipl --> "swipl-", !.
493swipl --> "pl-".
494
495opt_release(Rel) --> "-", int(Rel, 4), !.
496opt_release(-) --> "".
497
498opt_devel --> "devel-", !.
499opt_devel --> "".
500
501opt_cpu(x86_64) --> ".", "x86_64", !.
502opt_cpu(unknown) --> "".
503
504macos_version(tiger) --> "tiger".
505macos_version(leopard) --> "leopard".
506macos_version(snow_leopard) --> "snow-leopard".
507macos_version(lion) --> "lion".
508
509macos_cpu(ppc) --> "powerpc".
510macos_cpu(intel) --> "intel".
511macos_cpu(x86) --> "32bit".
512
513macos_def_cpu(snow_leopard, intel) :- !.
514macos_def_cpu(lion, intel) :- !.
515macos_def_cpu(_, ppc).
516
517win_type(win32) --> "w32".
518win_type(win64) --> "w64".
519
520cmake_win_type(win64) --> ".", "x64".
521cmake_win_type(arm64) --> ".", "arm64".
522cmake_win_type(win32) --> ".", "x86".
523
524long_version(version(Major, Minor, Patch, Tag)) -->
525 int(Major, 2), ".", int(Minor, 2), ".", int(Patch, 2), !,
526 tag(Tag),
527 !.
528long_version(latest) -->
529 "latest".
530
531tag(Tag) -->
532 "-", alnums(Codes),
533 { atom_codes(Tag, Codes),
534 \+ notag(Tag)
535 },
536 !.
537tag('') --> "".
538
539notag(fat).
540notag(arm64).
541
542int(Value, MaxDigits) -->
543 digits(Digits),
544 { length(Digits, Len),
545 Len =< MaxDigits,
546 Len > 0,
547 number_codes(Value, Digits)
548 }.
549
550alnums([H|T]) -->
551 [H], { code_type(H, alnum) }, !,
552 alnums(T).
553alnums([]) --> "".
554
555short_version(version(Major, Minor, Patch, Tag)) -->
556 digits(Digits),
557 { Digits = [D1,D2,D3]
558 -> number_codes(Major, [D1]),
559 number_codes(Minor, [D2]),
560 number_codes(Patch, [D3])
561 ; Digits = [D1,D2,D3,D4]
562 -> ( number_codes(51, [D1,D2]) 563 -> number_codes(Major, [D1]),
564 number_codes(Minor, [D2,D3]),
565 number_codes(Patch, [D4])
566 ; number_codes(Major, [D1]),
567 number_codes(Minor, [D2]),
568 number_codes(Patch, [D3,D4])
569 )
570 ; Digits = [D1,D2,D3,D4,D5]
571 -> number_codes(Major, [D1]),
572 number_codes(Minor, [D2,D3]),
573 number_codes(Patch, [D4,D5])
574 },
575 tag(Tag), !.
576short_version(latest) -->
577 "latest".
578
588
589sort_files(In, Out, Options) :-
590 map_list_to_pairs(map_type, In, Typed0),
591 ( option(show(all), Options)
592 -> Typed = Typed0
593 ; exclude(old_tagged_file, Typed0, Typed)
594 ),
595 keysort(Typed, TSorted),
596 group_pairs_by_key(TSorted, TGrouped),
597 maplist(sort_group_by_version, TGrouped, TGroupSorted),
598 ( option(show(all), Options)
599 -> pairs_values(TGroupSorted, TValues),
600 flatten(TValues, Out)
601 ; take_latest(TGroupSorted, Out)
602 ).
603
604map_type(File, Tag) :-
605 File = file(Type, Platform, _Version, _Name, _Path),
606 type_tag(Type, Platform, Tag).
607
608type_tag(bin, linux(A), tag(10, linux(A))) :- !.
609type_tag(bin, linux(A,B), tag(11, linux(A,B))) :- !.
610type_tag(bin, windows(A), tag(Tg, windows(A))) :- !,
611 win_tag(A, Tg2),
612 Tg is 20+Tg2.
613type_tag(bin, macos(A,B), tag(Tg, macos(Group,B))) :- !,
614 mac_tag(A, Group, Tg2),
615 Tg is 30+Tg2.
616type_tag(src, Format, tag(40, Format)) :- !.
617type_tag(doc, Format, tag(50, Format)) :- !.
618type_tag(X, Y, tag(60, X-Y)).
619
620mac_tag(installer, modern, 4).
621mac_tag(bundle, modern, 4).
622mac_tag(snow_leopard_and_later, obsolete, 5).
623mac_tag(lion, obsolete, 6).
624mac_tag(snow_leopard, obsolete, 7).
625mac_tag(leopard, obsolete, 8).
626mac_tag(tiger, obsolete, 9).
627
628win_tag(win64, 1).
629win_tag(arm64, 2).
630win_tag(win32, 3).
631
632sort_group_by_version(Tag-Files, Tag-Sorted) :-
633 map_list_to_pairs(tag_version, Files, TFiles),
634 keysort(TFiles, TRevSorted),
635 pairs_values(TRevSorted, RevSorted),
636 reverse(RevSorted, Sorted).
637
638tag_version(File, Tag) :-
639 File = file(_,_,Version,_,_),
640 version_tag(Version, Tag).
641
642version_tag(version(Major, Minor, Patch, Tag),
643 version(Major, Minor, Patch, Order)) :-
644 ( pre_version(Tag, Order)
645 -> true
646 ; print_message(error,
647 error(domain_error(pre_release_version, Tag),_)),
648 Order = pre(-100, 0)
649 ).
650
651pre_version('', pre(0, 0)) :- !.
652pre_version(NrA, pre(0, 0)) :-
653 atom_number(NrA, _Nr), !.
654pre_version(Tag, pre(TagOrder, N)) :-
655 tag(TagPrefix, TagOrder),
656 atom_concat(TagPrefix, NA, Tag),
657 atom_number(NA, N).
658
659tag(rc, -1).
660tag(beta, -2).
661tag(alpha, -3).
662
663take_latest([], []).
664take_latest([_-[H|_]|T0], [H|T]) :- !,
665 take_latest(T0, T).
666take_latest([_-[]|T0], T) :- !, 667 take_latest(T0, T).
668
670
671old_tagged_file(tag(_,Type)-_File) :-
672 old_file_type(Type).
673
674old_file_type(linux(_)).
675old_file_type(linux(_,_)).
676old_file_type(windows(win32)).
677old_file_type(macos(_,ppc)).
678old_file_type(macos(obsolete,_)).
679
680
681 684
693
694download(Request) :-
695 memberchk(path_info(Download), Request),
696 file_name_extension(File, envelope, Download), !,
697 envelope(File).
698download(Request) :-
699 memberchk(path_info(Download), Request),
700 ( file_name_extension(File, sha256, Download)
701 -> true
702 ; File = Download
703 ),
704 download_file(File, AbsFile),
705 cors_enable,
706 format('Cross-origin-resource-policy: cross-origin\n'),
707 ( File == Download
708 -> http_peer(Request, Remote),
709 broadcast(download(File, Remote)),
710 http_reply_file(AbsFile, [unsafe(true)], Request)
711 ; file_checksum(AbsFile, SHA256),
712 format('Content-type: text/plain~n~n'),
713 format('~w~n', [SHA256])
714 ).
715download(Request) :-
716 memberchk(path_info(Download), Request),
717 classify_file(Download, file(Class,Platform,latest,_,_), [show(last)]),
718 file_directory_name(Download, Dir),
719 absolute_file_name(download(Dir),
720 AbsDir,
721 [ access(read),
722 file_type(directory),
723 file_errors(fail)
724 ]),
725 download_files(AbsDir, Class, Files, [show(last)]),
726 memberchk(file(Class, Platform, _, File, _), Files), !,
727 directory_file_path(Dir, File, Redirect),
728 http_link_to_id(download, path_postfix(Redirect), URI),
729 http_redirect(see_other, URI, Request).
730download(Request) :-
731 ( memberchk(path_info(Download), Request)
732 -> http_safe_file(download(Download), [])
733 ; Download = '.'
734 ),
735 absolute_file_name(download(Download),
736 AbsFile,
737 [ access(read),
738 file_errors(fail),
739 file_type(directory)
740 ]), !,
741 http_reply_dirindex(AbsFile,
742 [ unsafe(true),
743 name(name_cell)
744 ], Request).
745download(Request) :-
746 memberchk(path(Path), Request),
747 existence_error(http_location, Path).
748
749download_file(File, AbsFile) :-
750 http_safe_file(download(File), []),
751 absolute_file_name(download(File),
752 AbsFile,
753 [ access(read),
754 file_errors(fail)
755 ]).
756
757:- public
758 name_cell//1. 759
760name_cell(File) -->
761 { needs_envelope(File),
762 file_base_name(File, Name),
763 uri_encoded(path, Name, Ref0),
764 file_name_extension(Ref0, envelope, Ref)
765 },
766 html(a(href(Ref), Name)).
767name_cell(File) -->
768 { file_base_name(File, Name),
769 uri_encoded(path, Name, Ref)
770 },
771 html(a(href(Ref), Name)).
772
776
777download_daily(_Request) :-
778 absolute_file_name(download('daily/bin'), Dir,
779 [ file_type(directory),
780 access(read)
781 ]),
782 reply_html_page(
783 download(Dir, 'Download daily builds for Windows'),
784 title('Download daily builds for Windows'),
785 [ \explain_win_daily,
786 \directory_index(Dir,
787 [ order_by(time),
788 order(descending),
789 name(name_cell)
790 ])
791 ]).
792
793
794explain_win_daily -->
795 html({|html||
796 <p>The table below provides access to the most recent 7
797 daily builds of SWI-Prolog for Windows. The build is done
798 automatically from the
799 <a href="https://github.com/SWI-Prolog/swipl-devel.git">GIT sources</a>.
800 The files use the following naming convention:
801 </p>
802 <ul>
803 <li><code>swipl-w64-</code><var>date</var><code>.exe</code>
804 </ul>
805 <p>
806 Please note that these versions <b>may be unstable!</b> It is
807 adviced to follow current discussions on the
808 <a href="https://swi-prolog.discourse.group/">SWI-Prolog Discourse
809 forum</a> and/or the git commit messages.
810 The primary purpose of the daily builds is to quickly provide
811 binaries after a bug report.
812 </p>
813 |}).
814
815
816 819
820needs_envelope(File) :-
821 file_name_extension(_, exe, File).
822
823add_envelope(File, Envelope) :-
824 needs_envelope(File),
825 !,
826 file_name_extension(File, envelope, Envelope).
827add_envelope(File, File).
828
829envelope(File) :-
830 maybe(0.1),
831 download_file(File, AbsFile),
832 file_checksum(AbsFile, OkHash),
833 compute_file_checksum(AbsFile, NewHash),
834 NewHash \== OkHash,
835 !,
836 reply_html_page(
837 download(File, 'Possibly tampered binary'),
838 title('Possibly tampered binary'),
839 \tampered(File, OkHash, NewHash)).
840envelope(File) :-
841 file_base_name(File, Base),
842 reply_html_page(
843 download(Base, 'Download binary'),
844 title('Download a binary file'),
845 \envelope(File)).
846
847envelope(File) -->
848 { http_absolute_location(icons('alert.gif'), Alert, []),
849 http_absolute_location(icons('vt_logo.png'), VTLogo, []),
850 download_file(File, AbsFile),
851 file_checksum(AbsFile, Hash),
852 file_base_name(File, Base),
853 format(atom(VTHREF), 'https://www.virustotal.com/file/~w/analysis/', Hash)
854 },
855 html({|html(Base, Hash, VTHREF, VTLogo, Alert)||
856<p><img src=Alert style="float:left">
857Windows antivirus software works using <i>signatures</i> and <i>heuristics</i>.
858Using the huge amount of virusses and malware known today, arbitrary executables
859are often <a href="https://en.wikipedia.org/wiki/Antivirus_software#Problems_caused_by_false_positives">falsily classified as malicious</a>.
860<a href="https://safebrowsing.google.com/">Google Safe Browsing</a>, used by
861most modern browsers, therefore often classifies our Windows binaries as
862malware. 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.
863</p>
864
865<p>
866Our Windows binaries are cross-compiled on an isolated Linux container. The
867integrity of the binaries on the server is regularly verified by validating its
868SHA256 fingerprint.
869</p>
870
871<p>
872Please select the checkbox below to enable the actual download link.
873</p>
874
875<table>
876<tr><td><input type="checkbox" id="understand"><td>I understand</tr>
877<tr><td><td><a id="download">Download <code>Base</code></a>
878<span style="color:#888; font-size:small;">(SHA256: <code>Hash</code>)</span></tr>
879<tr><td style="text-align:right"><img src=VTLogo style="width:1.5ex"><td><a href=VTHREF>VIRUSTOTAL Scan Result</a></tr>
880</table>
881 |}),
882 js_script({|javascript(Base)||
883$(function() {
884 $("#understand").prop("checked", false)
885 .on("click", function() {
886 $("#download").attr("href", Base);
887 });
888});
889
890 |}).
891
892tampered(File, OkHash, NewHash) -->
893 { http_absolute_location(icons('alert.gif'), Alert, [])
894 },
895 html({|html(File, Alert, OkHash, NewHash)||
896<p><img src=Alert style="float:left">
897The file <code>File</code> SHA256 signature has changed. Please
898report this at <a href="mailto:bugs@swi-prolog.org">bugs@swi-prolog.org</a>
899 |}).
900
901
902 905
906:- persistent
907 sha256(path:atom,
908 sha256:atom). 909
910checksum_file(File) :-
911 absolute_file_name(data('checksum.db'), File,
912 [ access(write) ]).
913
914attach_db :-
915 checksum_file(File),
916 ( db_attached(File)
917 -> true
918 ; db_attach(File, [])
919 ).
920
931
932file_checksum(Path, Sum) :-
933 attach_db,
934 sha256(Path, Sum0), !,
935 Sum = Sum0.
936file_checksum(Path, Sum) :-
937 compute_file_checksum(Path, Sum).
938
939compute_file_checksum(Path, Sum) :-
940 crypto_file_hash(Path, Sum,
941 [ encoding(octet),
942 algorithm(sha256)
943 ]),
944 assert_sha256(Path, Sum)