272 lines
8.9 KiB
Prolog
272 lines
8.9 KiB
Prolog
/* Part of SWI-Prolog
|
|
|
|
Author: Jan Wielemaker
|
|
E-mail: J.Wielemaker@cs.vu.nl
|
|
WWW: http://www.swi-prolog.org
|
|
Copyright (C): 2009, VU University, Amsterdam
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
As a special exception, if you link this library with other files,
|
|
compiled with a Free Software compiler, to produce an executable, this
|
|
library does not by itself cause the resulting executable to be covered
|
|
by the GNU General Public License. This exception does not however
|
|
invalidate any other reasons why the executable file might be covered by
|
|
the GNU General Public License.
|
|
*/
|
|
|
|
:- module(http_pwp,
|
|
[ reply_pwp_page/3, % :File, +Options, +Request
|
|
pwp_handler/2 % +Options, +Request
|
|
]).
|
|
:- use_module(library(http/http_dispatch)).
|
|
:- use_module(library(sgml)).
|
|
:- use_module(library(sgml_write)).
|
|
:- use_module(library(option)).
|
|
:- use_module(library(error)).
|
|
:- use_module(library(lists)).
|
|
:- use_module(library(pwp)).
|
|
|
|
/** <module> Serve PWP pages through the HTTP server
|
|
|
|
This module provides convience predicates to include PWP (Prolog
|
|
Well-formed Pages) in a Prolog web-server. It provides the following
|
|
predicates:
|
|
|
|
* pwp_handler/2
|
|
This is a complete web-server aimed at serving static pages, some
|
|
of which include PWP. This API is intended to allow for programming
|
|
the web-server from a hierarchy of pwp files, prolog files and static
|
|
web-pages.
|
|
|
|
* reply_pwp_page/3
|
|
Return a single PWP page that is executed in the context of the calling
|
|
module. This API is intended for individual pages that include so much
|
|
text that generating from Prolog is undesirable.
|
|
|
|
@tbd Support elements in the HTML header that allow controlling the
|
|
page, such as setting the CGI-header, authorization, etc.
|
|
@tbd Allow external styling. Pass through reply_html_page/2? Allow
|
|
filtering the DOM before/after PWP?
|
|
*/
|
|
|
|
%% pwp_handler(+Options, +Request)
|
|
%
|
|
% Handle PWP files. This predicate is defined to create a simple
|
|
% HTTP server from a hierarchy of PWP, HTML and other files. The
|
|
% interface is kept compatible with the
|
|
% library(http/http_dispatch). In the typical usage scenario, one
|
|
% needs to define an http location and a file-search path that is
|
|
% used as the root of the server. E.g., the following declarations
|
|
% create a self-contained web-server for files in =|/web/pwp/|=.
|
|
%
|
|
% ==
|
|
% user:file_search_path(pwp, '/web/pwp').
|
|
%
|
|
% :- http_handler(root(.), pwp_handler([path_alias(pwp)]), [prefix]).
|
|
% ==
|
|
%
|
|
% Options include:
|
|
%
|
|
% * path_alias(+Alias)
|
|
% Search for PWP files as Alias(Path). See absolute_file_name/3.
|
|
% * index(+Index)
|
|
% Name of the directory index (pwp) file. This option may
|
|
% appear multiple times. If no such option is provided,
|
|
% pwp_handler/2 looks for =|index.pwp|=.
|
|
% * view(+Boolean)
|
|
% If =true= (default is =false=), allow for ?view=source to serve
|
|
% PWP file as source.
|
|
% * index_hook(:Hook)
|
|
% If a directory has no index-file, pwp_handler/2 calls
|
|
% Hook(PhysicalDir, Options, Request). If this semidet
|
|
% predicate succeeds, the request is considered handled.
|
|
% * hide_extensions(+List)
|
|
% Hide files of the given extensions. The default is to
|
|
% hide .pl files.
|
|
%
|
|
% @see reply_pwp_page/3
|
|
% @error permission_error(index, http_location, Location) is
|
|
% raised if the handler resolves to a directory that has no
|
|
% index.
|
|
|
|
:- meta_predicate
|
|
pwp_handler(:, +).
|
|
|
|
pwp_handler(QOptions, Request) :-
|
|
meta_options(is_meta, QOptions, Options),
|
|
( memberchk(path_info(Spec), Request)
|
|
-> true
|
|
; Spec = '.'
|
|
),
|
|
( option(path_alias(Alias), Options)
|
|
-> Term =.. [Alias,Spec]
|
|
; Term = Spec
|
|
),
|
|
http_safe_file(Term, Options),
|
|
absolute_file_name(Term, Path, [access(read)]),
|
|
( exists_directory(Path)
|
|
-> ensure_slash(Path, Dir),
|
|
( ( member(index(Index), Options)
|
|
*-> true
|
|
; Index = 'index.pwp'
|
|
),
|
|
atom_concat(Dir, Index, File),
|
|
access_file(File, read)
|
|
-> true
|
|
; option(index_hook(Hook), Options),
|
|
call(Hook, Path, Options, Request)
|
|
-> true
|
|
; memberchk(path(Location), Request),
|
|
permission_error(index, http_location, Location)
|
|
)
|
|
; File = Path
|
|
),
|
|
server_file(File, Request, Options).
|
|
|
|
is_meta(index_hook).
|
|
|
|
server_file(File, _, _) :- % index-hook did the work
|
|
var(File), !.
|
|
server_file(File, Request, Options) :-
|
|
file_name_extension(_, pwp, File), !,
|
|
( option(view(true), Options),
|
|
memberchk(search(Query), Request),
|
|
memberchk(view=source, Query)
|
|
-> http_reply_file(File, [ mime_type(text/plain),
|
|
unsafe(true)
|
|
], Request)
|
|
; merge_options(Options,
|
|
[ pwp_module(true)
|
|
], Opts),
|
|
reply_pwp_page(File, [unsafe(true)|Opts], Request)
|
|
).
|
|
server_file(File, Request, Options) :-
|
|
option(hide_extensions(Exts), Options, [pl]),
|
|
file_name_extension(_, Ext, File),
|
|
( memberchk(Ext, Exts)
|
|
-> memberchk(path(Location), Request),
|
|
permission_error(read, http_location, Location)
|
|
; http_reply_file(File, [unsafe(true)|Options], Request)
|
|
).
|
|
|
|
|
|
ensure_slash(Path, Dir) :-
|
|
( sub_atom(Path, _, _, 0, /)
|
|
-> Dir = Path
|
|
; atom_concat(Path, /, Dir)
|
|
).
|
|
|
|
|
|
%% reply_pwp_page(:File, +Options, +Request)
|
|
%
|
|
% Reply a PWP file. This interface is provided to server
|
|
% individual locations from PWP files. Using a PWP file rather
|
|
% than generating the page from Prolog may be desirable because
|
|
% the page contains a lot of text (which is cumbersome to generate
|
|
% from Prolog) or because the maintainer is not familiar with
|
|
% Prolog.
|
|
%
|
|
% Options supported are:
|
|
%
|
|
% * mime_type(+Type)
|
|
% Serve the file using the given mime-type. Default is
|
|
% text/html.
|
|
% * unsafe(+Boolean)
|
|
% Passed to http_safe_file/2 to check for unsafe paths.
|
|
% * pwp_module(+Boolean)
|
|
% If =true=, (default =false=), process the PWP file in
|
|
% a module constructed from its canonical absolute path.
|
|
% Otherwise, the PWP file is processed in the calling
|
|
% module.
|
|
%
|
|
% Initial context:
|
|
%
|
|
% * SCRIPT_NAME
|
|
% Virtual path of the script.
|
|
% * SCRIPT_DIRECTORY
|
|
% Physical directory where the script lives
|
|
% * QUERY
|
|
% Var=Value list representing the query-parameters
|
|
% * REMOTE_USER
|
|
% If access has been authenticated, this is the authenticated
|
|
% user.
|
|
% * REQUEST_METHOD
|
|
% One of =get=, =post=, =put= or =head=
|
|
% * CONTENT_TYPE
|
|
% Content-type provided with HTTP POST and PUT requests
|
|
% * CONTENT_LENGTH
|
|
% Content-length provided with HTTP POST and PUT requests
|
|
%
|
|
% While processing the script, the file-search-path pwp includes
|
|
% the current location of the script. I.e., the following will
|
|
% find myprolog in the same directory as where the PWP file
|
|
% resides.
|
|
%
|
|
% ==
|
|
% pwp:ask="ensure_loaded(pwp(myprolog))"
|
|
% ==
|
|
%
|
|
% @tbd complete the initial context, as far as possible from CGI
|
|
% variables. See http://hoohoo.ncsa.illinois.edu/docs/cgi/env.html
|
|
% @see pwp_handler/2.
|
|
|
|
:- meta_predicate
|
|
reply_pwp_page(:, +, +).
|
|
|
|
reply_pwp_page(M:File, Options, Request) :-
|
|
http_safe_file(File, Options),
|
|
absolute_file_name(File, Path,
|
|
[ access(read)
|
|
]),
|
|
memberchk(method(Method), Request),
|
|
file_directory_name(Path, Dir),
|
|
load_xml_file(Path, Contents),
|
|
findall(C, pwp_context(Request, C), Context),
|
|
( option(pwp_module(true), Options)
|
|
-> PWP_M = Path
|
|
; PWP_M = M
|
|
),
|
|
setup_call_cleanup(asserta(script_dir(Dir), Ref),
|
|
pwp_xml(PWP_M:Contents, Transformed,
|
|
[ 'REQUEST_METHOD' = Method,
|
|
'SCRIPT_DIRECTORY' = Dir
|
|
| Context
|
|
]),
|
|
erase(Ref)),
|
|
option(mime_type(Type), Options, text/html),
|
|
format('Content-type: ~w~n~n', [Type]),
|
|
xml_write(current_output, Transformed, []).
|
|
|
|
pwp_context(Request, 'REMOTE_USER' = User) :-
|
|
memberchk(user(User), Request).
|
|
pwp_context(Request, 'QUERY' = Query) :-
|
|
memberchk(search(Query), Request).
|
|
pwp_context(Request, 'SCRIPT_NAME' = Path) :-
|
|
memberchk(path(Path), Request).
|
|
pwp_context(Request, 'CONTENT_TYPE' = ContentType) :-
|
|
memberchk(content_type(ContentType), Request).
|
|
pwp_context(Request, 'CONTENT_LENGTH' = Length) :-
|
|
memberchk(content_length(Length), Request).
|
|
|
|
:- multifile user:file_search_path/2.
|
|
:- dynamic user:file_search_path/2.
|
|
:- thread_local script_dir/1.
|
|
|
|
user:file_search_path(pwp, ScriptDir) :-
|
|
script_dir(ScriptDir).
|
|
|
|
|