View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2008-2014, University of Amsterdam
    7                              VU University Amsterdam
    8    All rights reserved.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(http_path,
   37          [ http_absolute_uri/2,        % +Spec, -URI
   38            http_absolute_location/3,   % +Spec, -Path, +Options
   39            http_clean_location_cache/0
   40          ]).   41:- use_module(library(lists)).   42:- use_module(library(error)).   43:- use_module(library(apply)).   44:- use_module(library(debug)).   45:- use_module(library(option)).   46:- use_module(library(settings)).   47:- use_module(library(broadcast)).   48:- use_module(library(uri)).   49:- use_module(library(http/http_host)).   50:- use_module(library(http/http_wrapper)).   51
   52
   53:- predicate_options(http_absolute_location/3, 3, [relative_to(atom)]).   54
   55/** <module> Abstract specification of HTTP server locations
   56
   57This module provides an abstract specification  of HTTP server locations
   58that is inspired on absolute_file_name/3. The   specification is done by
   59adding rules to the  dynamic   multifile  predicate http:location/3. The
   60speficiation is very similar to   user:file_search_path/2,  but takes an
   61additional argument with options. Currently only one option is defined:
   62
   63    * priority(+Integer)
   64    If two rules match, take the one with highest priority.  Using
   65    priorities is needed because we want to be able to overrule
   66    paths, but we do not want to become dependent on clause ordering.
   67
   68    The default priority is 0. Note however that notably libraries may
   69    decide to provide a fall-back using a negative priority.  We suggest
   70    -100 for such cases.
   71
   72This library predefines a single location at priority -100:
   73
   74    * root
   75    The root of the server.  Default is /, but this may be overruled
   76    using the setting (see setting/2) =|http:prefix|=
   77
   78To serve additional resource files such as CSS, JavaScript and icons,
   79see `library(http/http_server_files)`.
   80
   81Here is an example that binds =|/login|=  to login/1. The user can reuse
   82this application while moving all locations  using   a  new rule for the
   83admin location with the option =|[priority(10)]|=.
   84
   85  ==
   86  :- multifile http:location/3.
   87  :- dynamic   http:location/3.
   88
   89  http:location(admin, /, []).
   90
   91  :- http_handler(admin(login), login, []).
   92
   93  login(Request) :-
   94          ...
   95  ==
   96*/
   97
   98:- setting(http:prefix, atom, '',
   99           'Prefix for all locations of this server').  100
  101%!  http:location(+Alias, -Expansion, -Options) is nondet.
  102%
  103%   Multifile hook used to specify new  HTTP locations. Alias is the
  104%   name  of  the  abstract  path.  Expansion    is  either  a  term
  105%   Alias2(Relative), telling http_absolute_location/3  to translate
  106%   Alias by first translating Alias2 and then applying the relative
  107%   path Relative or, Expansion is an   absolute location, i.e., one
  108%   that starts with a =|/|=. Options   currently  only supports the
  109%   priority  of  the  path.  If  http:location/3  returns  multiple
  110%   solutions the one with the  highest   priority  is selected. The
  111%   default priority is 0.
  112%
  113%   This library provides  a  default   for  the  abstract  location
  114%   =root=. This defaults to the setting   http:prefix  or, when not
  115%   available to the  path  =|/|=.  It   is  adviced  to  define all
  116%   locations (ultimately) relative to  =root=.   For  example,  use
  117%   root('home.html') rather than =|'/home.html'|=.
  118
  119:- multifile
  120    http:location/3.                % Alias, Expansion, Options
  121:- dynamic
  122    http:location/3.                % Alias, Expansion, Options
  123
  124http:location(root, Root, [priority(-100)]) :-
  125    (   setting(http:prefix, Prefix),
  126        Prefix \== ''
  127    ->  Root = Prefix
  128    ;   Root = (/)
  129    ).
  130
  131
  132%!  http_absolute_uri(+Spec, -URI) is det.
  133%
  134%   URI is the absolute (i.e., starting   with  =|http://|=) URI for
  135%   the abstract specification Spec. Use http_absolute_location/3 to
  136%   create references to locations on the same server.
  137%
  138%   @tbd    Distinguish =http= from =https=
  139
  140http_absolute_uri(Spec, URI) :-
  141    http_current_host(_Request, Host, Port,
  142                      [ global(true)
  143                      ]),
  144    http_absolute_location(Spec, Path, []),
  145    uri_authority_data(host, AuthC, Host),
  146    (   Port == 80                  % HTTP scheme
  147    ->  true
  148    ;   uri_authority_data(port, AuthC, Port)
  149    ),
  150    uri_authority_components(Authority, AuthC),
  151    uri_data(path, Components, Path),
  152    uri_data(scheme, Components, http),
  153    uri_data(authority, Components, Authority),
  154    uri_components(URI, Components).
  155
  156
  157%!  http_absolute_location(+Spec, -Path, +Options) is det.
  158%
  159%   Path is the HTTP location for the abstract specification Spec.
  160%   Options:
  161%
  162%       * relative_to(Base)
  163%       Path is made relative to Base.  Default is to generate
  164%       absolute URLs.
  165%
  166%   @see     http_absolute_uri/2 to create a reference that can be
  167%            used on another server.
  168
  169:- dynamic
  170    location_cache/3.  171
  172http_absolute_location(Spec, Path, Options) :-
  173    must_be(ground, Spec),
  174    option(relative_to(Base), Options, /),
  175    absolute_location(Spec, Base, Path, Options),
  176    debug(http_path, '~q (~q) --> ~q', [Spec, Base, Path]).
  177
  178absolute_location(Spec, Base, Path, _Options) :-
  179    location_cache(Spec, Base, Cache),
  180    !,
  181    Path = Cache.
  182absolute_location(Spec, Base, Path, Options) :-
  183    expand_location(Spec, Base, L, Options),
  184    assert(location_cache(Spec, Base, L)),
  185    Path = L.
  186
  187expand_location(Spec, Base, Path, _Options) :-
  188    atomic(Spec),
  189    !,
  190    (   uri_components(Spec, Components),
  191        uri_data(scheme, Components, Scheme),
  192        atom(Scheme)
  193    ->  Path = Spec
  194    ;   relative_to(Base, Spec, Path)
  195    ).
  196expand_location(Spec, _Base, Path, Options) :-
  197    Spec =.. [Alias, Sub],
  198    http_location_path(Alias, Parent),
  199    absolute_location(Parent, /, ParentLocation, Options),
  200    phrase(path_list(Sub), List),
  201    atomic_list_concat(List, /, SubAtom),
  202    (   ParentLocation == ''
  203    ->  Path = SubAtom
  204    ;   sub_atom(ParentLocation, _, _, 0, /)
  205    ->  atom_concat(ParentLocation, SubAtom, Path)
  206    ;   atomic_list_concat([ParentLocation, SubAtom], /, Path)
  207    ).
  208
  209
  210%!  http_location_path(+Alias, -Expansion) is det.
  211%
  212%   Expansion is the expanded HTTP location for Alias. As we have no
  213%   condition search, we demand a single  expansion for an alias. An
  214%   ambiguous alias results in a printed   warning.  A lacking alias
  215%   results in an exception.
  216%
  217%   @error  existence_error(http_alias, Alias)
  218
  219http_location_path(Alias, Path) :-
  220    findall(P-L, http_location_path(Alias, L, P), Pairs),
  221    sort(Pairs, Sorted0),
  222    reverse(Sorted0, Result),
  223    (   Result = [_-One]
  224    ->  Path = One
  225    ;   Result == []
  226    ->  existence_error(http_alias, Alias)
  227    ;   Result = [P-Best,P2-_|_],
  228        P \== P2
  229    ->  Path = Best
  230    ;   Result = [_-First|_],
  231        pairs_values(Result, Paths),
  232        print_message(warning, http(ambiguous_location(Alias, Paths))),
  233        Path = First
  234    ).
  235
  236
  237%!  http_location_path(+Alias, -Path, -Priority) is nondet.
  238%
  239%   @tbd    prefix(Path) is discouraged; use root(Path)
  240
  241http_location_path(Alias, Path, Priority) :-
  242    http:location(Alias, Path, Options),
  243    option(priority(Priority), Options, 0).
  244http_location_path(prefix, Path, 0) :-
  245    (   catch(setting(http:prefix, Prefix), _, fail),
  246        Prefix \== ''
  247    ->  (   sub_atom(Prefix, 0, _, _, /)
  248        ->  Path = Prefix
  249        ;   atom_concat(/, Prefix, Path)
  250        )
  251    ;   Path = /
  252    ).
  253
  254
  255%!  relative_to(+Base, +Path, -AbsPath) is det.
  256%
  257%   AbsPath is an absolute URL location created from Base and Path.
  258%   The result is cleaned
  259
  260relative_to(/, Path, Path) :- !.
  261relative_to(_Base, Path, Path) :-
  262    sub_atom(Path, 0, _, _, /),
  263    !.
  264relative_to(Base, Local, Path) :-
  265    sub_atom(Base, 0, _, _, /),    % file version
  266    !,
  267    path_segments(Base, BaseSegments),
  268    append(BaseDir, [_], BaseSegments) ->
  269    path_segments(Local, LocalSegments),
  270    append(BaseDir, LocalSegments, Segments0),
  271    clean_segments(Segments0, Segments),
  272    path_segments(Path, Segments).
  273relative_to(Base, Local, Global) :-
  274    uri_normalized(Local, Base, Global).
  275
  276path_segments(Path, Segments) :-
  277    atomic_list_concat(Segments, /, Path).
  278
  279%!  clean_segments(+SegmentsIn, -SegmentsOut) is det.
  280%
  281%   Clean a path represented  as  a   segment  list,  removing empty
  282%   segments and resolving .. based on syntax.
  283
  284clean_segments([''|T0], [''|T]) :-
  285    !,
  286    exclude(empty_segment, T0, T1),
  287    clean_parent_segments(T1, T).
  288clean_segments(T0, T) :-
  289    exclude(empty_segment, T0, T1),
  290    clean_parent_segments(T1, T).
  291
  292clean_parent_segments([], []).
  293clean_parent_segments([..|T0], T) :-
  294    !,
  295    clean_parent_segments(T0, T).
  296clean_parent_segments([_,..|T0], T) :-
  297    !,
  298    clean_parent_segments(T0, T).
  299clean_parent_segments([H|T0], [H|T]) :-
  300    clean_parent_segments(T0, T).
  301
  302empty_segment('').
  303empty_segment('.').
  304
  305
  306%!  path_list(+Spec, -List) is det.
  307%
  308%   Translate seg1/seg2/... into [seg1,seg2,...].
  309%
  310%   @error  instantiation_error
  311%   @error  type_error(atomic, X)
  312
  313path_list(Var) -->
  314    { var(Var),
  315      !,
  316      instantiation_error(Var)
  317    }.
  318path_list(A/B) -->
  319    !,
  320    path_list(A),
  321    path_list(B).
  322path_list(.) -->
  323    !,
  324    [].
  325path_list(A) -->
  326    { must_be(atomic, A) },
  327    [A].
  328
  329
  330                 /*******************************
  331                 *            MESSAGES          *
  332                 *******************************/
  333
  334:- multifile
  335    prolog:message/3.  336
  337prolog:message(http(ambiguous_location(Spec, Paths))) -->
  338    [ 'http_absolute_location/2: ambiguous specification: ~q: ~p'-
  339      [Spec, Paths]
  340    ].
  341
  342
  343                 /*******************************
  344                 *        CACHE CLEANUP         *
  345                 *******************************/
  346
  347%!  http_clean_location_cache
  348%
  349%   HTTP locations resolved  through   http_absolute_location/3  are
  350%   cached.  This  predicate  wipes   the    cache.   The  cache  is
  351%   automatically wiped by make/0 and if  the setting http:prefix is
  352%   changed.
  353
  354http_clean_location_cache :-
  355    retractall(location_cache(_,_,_)).
  356
  357:- listen(settings(changed(http:prefix, _, _)),
  358          http_clean_location_cache).  359
  360:- multifile
  361    user:message_hook/3.  362:- dynamic
  363    user:message_hook/3.  364
  365user:message_hook(make(done(Reload)), _Level, _Lines) :-
  366    Reload \== [],
  367    http_clean_location_cache,
  368    fail