1/* Part of SWI-Prolog 2 3 Author: Jan Wielemaker 4 E-mail: J.Wielemaker@cs.vu.nl 5 WWW: http://www.swi-prolog.org 6 Copyright (C): 2009-2026, VU University Amsterdam 7 CWI, Amsterdam 8 SWI-Prolog Solutions b.v. 9 10 This program is free software; you can redistribute it and/or 11 modify it under the terms of the GNU General Public License 12 as published by the Free Software Foundation; either version 2 13 of the License, or (at your option) any later version. 14 15 This program is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU General Public License for more details. 19 20 You should have received a copy of the GNU General Public 21 License along with this library; if not, write to the Free Software 22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23 24 As a special exception, if you link this library with other files, 25 compiled with a Free Software compiler, to produce an executable, this 26 library does not by itself cause the resulting executable to be covered 27 by the GNU General Public License. This exception does not however 28 invalidate any other reasons why the executable file might be covered by 29 the GNU General Public License. 30*/ 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).
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)]).
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)]).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).
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 [].
tr with Label and a tr row for each each
matching file. Options are:
all or latest (default).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(?). % no defined icon 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).
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 /******************************* 377 * CLASSIFY FILES * 378 *******************************/
385:- dynamic 386 download_cache/6. % Hash, Dir, Class, Opts, Time, Files 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).
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", !. 444% Cmake version 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]) % 5.1X.Y 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".
Options:
all or latest.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) :- !, % empty set 667 take_latest(T0, T).
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 /******************************* 682 * DOWNLOAD * 683 *******************************/
latest you get an HTTP
303 (See Other) reply pointing at the latest version.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)).
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 /******************************* 817 * ENVELOPE * 818 *******************************/ 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 /******************************* 903 * CHECKSUMS * 904 *******************************/ 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 ).
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)