View source with raw comments or as raw
    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).
 download(+Request) is det
HTTP handler for SWI-Prolog download pages.
   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)]).
 download_table(+Request)
Provide a table with possible download targets. test edit
   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)]).
 list_downloads(+Directory)
   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).
 toggle_show(+Options) is det
Add a toggle to switch between showing only the latest version and all versions.
  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	[].
 list_files(+Dir, +SubDir, +Class, +Label, +Options) is det
Create table rows for all files in Dir/SubDir. If files are present, emit a tr with Label and a tr row for each each matching file. Options are:
show(Show)
One of 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).
 platform_notes(+Platform, +Path) is det
Include notes on the platform. These notes are stored in a wiki file in the same directory as the download file.
  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		 *******************************/
 download_files(+Dir, +Class, -Files, +Options)
Files is a list of files that satisfy Class and Options in Dir/Subdir.
  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).
 classify_file(+Path, -Term, +Options) is semidet
  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".
 sort_files(+In, -Out, +Options)
Sort files by type and version. Type: linux, windows, mac, src, doc. Versions: latest first.

Options:

show(Show)
One of 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).
 old_tagged_file(+TypeFile) is semidet
  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		 *******************************/
 download(+Request) is det
Actually download a file. Two special requests are supported:
  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)).
 download_daily(+Request)
Provide the download page for the windows binaries.
  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	).
 file_checksum(+Path:atom, -Sum:atom) is det
True when Sum is the SHA256 checksum of file. We keep this in the Prolog database because this simplifies uploading files. Although the data under control of the server and thus more vulnerable than the download area on disk because that is not writeable by the server, I think this is also better from a security point of view because it requires the attacker to both modify the filesystem and the server, something that requires different rights and expertise.
  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)