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