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)  2009-2017, 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_exception,
   37          [ map_exception_to_http_status/4,     % +Exception, -Reply,
   38                                                % -HdrExtra, -Context
   39            in_or_exclude_backtrace/2           % +Error, -CleanedError
   40          ]).   41:- use_module(library(settings), [current_setting/1, setting/2]).   42
   43/** <module> Map Prolog exceptions to HTTP errors
   44
   45This module maps exceptions from various parts  of the HTTP libraries as
   46well as exceptions from user  handler   predicates  into meaningful HTTP
   47error codes such as 4XX and 5XX  codes. For example, existence errors on
   48http locations are mapped to 404 while out-of-stack is mapped to 503.
   49
   50This library provides two hooks:
   51
   52  - http:map_exception_to_http_status_hook/4 can be used to map
   53    additional exceptions to any HTTP status code.
   54  - http:bad_request_error/2 can be extended to map exceptions into 400
   55    bad request responses.
   56
   57@see    http_header.pl, http_wrapper.pl
   58*/
   59
   60:- multifile
   61    http:bad_request_error/2,                 % Formal, Context
   62    http:map_exception_to_http_status_hook/4. % Exception, Reply, HdrExtra, Ctx
   63
   64%!  map_exception_to_http_status(+Exception, -Reply, -HdrExtra, -Context)
   65%
   66%   Map certain exceptions to HTTP  status codes. The http(not_modified)
   67%   provides backward compatibility to http_reply(not_modified).
   68
   69map_exception_to_http_status(Exception, Reply, HdrExtra, Context) :-
   70    http:map_exception_to_http_status_hook(Exception, Reply, HdrExtra, Context),
   71    !.
   72map_exception_to_http_status(http(not_modified),
   73              not_modified,
   74              [connection('Keep-Alive')],
   75              []) :- !.
   76map_exception_to_http_status(http_reply(Reply),
   77              Reply,
   78              [connection(Close)],
   79              []) :-
   80    !,
   81    keep_alive(Reply, Close).
   82map_exception_to_http_status(http_reply(Reply, HdrExtra0),
   83              Reply,
   84              HdrExtra,
   85              Context) :-
   86    !,
   87    map_exception_to_http_status(http_reply(Reply, HdrExtra0, []),
   88                                 Reply,
   89                                 HdrExtra,
   90                                 Context).
   91
   92map_exception_to_http_status(http_reply(Reply, HdrExtra0, Context),
   93              Reply,
   94              HdrExtra,
   95              Context):-
   96    !,
   97    (   memberchk(connection(_), HdrExtra0)
   98    ->  HdrExtra = HdrExtra0
   99    ;   HdrExtra = [connection(Close)|HdrExtra0],
  100        keep_alive(Reply, Close)
  101    ).
  102map_exception_to_http_status(error(existence_error(http_location, Location), _),
  103              not_found(Location),
  104              [connection(close)],
  105              []) :- !.
  106map_exception_to_http_status(error(permission_error(http_method, Method, Location), _),
  107              method_not_allowed(Method, Location),
  108              [connection(close)],
  109              []) :- !.
  110map_exception_to_http_status(error(permission_error(_, http_location, Location), _),
  111              forbidden(Location),
  112              [connection(close)],
  113              []) :- !.
  114map_exception_to_http_status(error(threads_in_pool(_Pool), _),
  115              busy,
  116              [connection(close)],
  117              []) :- !.
  118map_exception_to_http_status(E,
  119              resource_error(E),
  120              [connection(close)],
  121              []) :-
  122    is_resource_error(E),
  123    !.
  124map_exception_to_http_status(E,
  125              bad_request(E2),
  126              [connection(close)],
  127              []) :-
  128    bad_request_exception(E),
  129    !,
  130    discard_stack_trace(E, E2).
  131map_exception_to_http_status(E,
  132              server_error(E),
  133              [connection(close)],
  134              []).
  135
  136is_resource_error(error(resource_error(_), _)).
  137
  138bad_request_exception(error(Error, Context)) :-
  139    nonvar(Error),
  140    bad_request_error(Error, ContextGeneral),
  141    (   var(ContextGeneral)
  142    ->  true
  143    ;   Context = context(_Stack, ContextInstance)
  144    ->  subsumes_term(ContextGeneral, ContextInstance)
  145    ),
  146    !.
  147
  148bad_request_error(Error, Context) :-
  149    http:bad_request_error(Error, Context).
  150bad_request_error(Error, Context) :-
  151    default_bad_request_error(Error, Context).
  152
  153default_bad_request_error(domain_error(http_request, _), _).
  154default_bad_request_error(existence_error(http_parameter, _), _).
  155default_bad_request_error(type_error(_, _), http_parameter(_)).
  156default_bad_request_error(syntax_error(http_request_line(_)), _).
  157default_bad_request_error(syntax_error(http_request(_)), _).
  158default_bad_request_error(syntax_error(_), in_http_request).
  159
  160discard_stack_trace(error(Formal, context(_,Msg)),
  161                    error(Formal, context(_,Msg))).
  162
  163%!  in_or_exclude_backtrace(+ErrorIn, -ErrorOut)
  164%
  165%   Remove  the  stacktrace  from  the   exception,  unless  setting
  166%   `http:client_backtrace` is `true`.
  167
  168in_or_exclude_backtrace(Error, Error) :-
  169    current_setting(http:client_backtrace),
  170    setting(http:client_backtrace, true),
  171    !.
  172in_or_exclude_backtrace(Error0, Error) :-
  173    discard_stack_trace(Error0, Error),
  174    !.
  175in_or_exclude_backtrace(Exception, Exception).
  176
  177
  178%!  http:bad_request_error(+Formal, -ContextTemplate) is semidet.
  179%
  180%   If  an  exception  of  the   term  error(Formal,  context(Stack,
  181%   Context)) is caught and  subsumes_term(ContextTemplate, Context)
  182%   is true, translate the exception into  an HTTP 400 exception. If
  183%   the exception contains a stack-trace, this  is stripped from the
  184%   response.
  185%
  186%   The idea behind this hook  is   that  applications can raise 400
  187%   responses by
  188%
  189%     - Throwing a specific (error) exception and adding a rule
  190%       to this predicate to interpret this as 400.
  191%     - Define rules for prolog:error_message//1 to formulate
  192%       an appropriate message.
  193
  194
  195%!  keep_alive(+Reply) is semidet.
  196%!  keep_alive(+Reply, -Connection) is det.
  197%
  198%   If true for Reply, the default is to keep the connection open.
  199
  200keep_alive(Reply, Connection) :-
  201    (   keep_alive(Reply)
  202    ->  Connection = 'Keep-Alive'
  203    ;   Connection = close
  204    ).
  205
  206keep_alive(not_modified).
  207keep_alive(bytes(_Type, _Bytes)).
  208keep_alive(file(_Type, _File)).
  209keep_alive(tmp_file(_Type, _File)).
  210keep_alive(stream(_In, _Len)).
  211keep_alive(cgi_stream(_In, _Len)).
  212keep_alive(switching_protocols(_Goal, _)).
  213
  214
  215                 /*******************************
  216                 *          IDE SUPPORT         *
  217                 *******************************/
  218
  219% See library('trace/exceptions')
  220
  221:- multifile
  222    prolog:general_exception/2.  223
  224prolog:general_exception(http_reply(_), http_reply(_)).
  225prolog:general_exception(http_reply(_,_), http_reply(_,_))