View source with raw comments or as raw
   1/*  Part of the SWI-Prolog HTTP package
   2
   3    Author:        Jan Wielemaker
   4    E-mail:        J.Wielemaker@vu.nl
   5    WWW:           http://www.swi-prolog.org
   6    Copyright (c)  2012-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_cors,
  37          [ cors_enable/0,
  38            cors_enable/2                       % +Requesy, +Options
  39          ]).
  40:- use_module(library(settings)).
  41
  42:- setting(http:cors, list(atom), [],
  43           'Enable CORS for the listed domains.  Use [*] for all domains').
  44
  45/** <module> Enable CORS: Cross-Origin Resource Sharing
  46
  47This small module allows  for   enabling  Cross-Origin  Resource Sharing
  48(CORS) for a specific  request.  Typically,   CORS  is  enabled  for API
  49services that you want to have useable  from browser client code that is
  50loaded from another domain. An example are   the LOD and SPARQL services
  51in ClioPatria.
  52
  53Because CORS is a security risc  (see   references),  it  is disabled by
  54default. It is enabled through the setting  http:cors. The value of this
  55setting is a list of domains  that   are  allowed to access the service.
  56Because * is used as a wildcard match,  the value [*] allows access from
  57anywhere.
  58
  59Services for which CORS is relevant must   call cors_enable/0 as part of
  60the HTTP response, as shown below. Note that cors_enable/0 is a no-op if
  61the setting http:cors is set to the empty list ([]).
  62
  63  ==
  64  my_handler(Request) :-
  65        ....,
  66        cors_enable,
  67        reply_json(Response, []).
  68  ==
  69
  70If a site uses a _Preflight_  =OPTIONS=   request  to  find the server's
  71capabilities and access politics, cors_enable/2 can be used to formulate
  72an appropriate reply.  For example:
  73
  74  ==
  75  my_handler(Request) :-
  76        option(method(options), Request), !,
  77        cors_enable(Request,
  78                    [ methods([get,post,delete])
  79                    ]),
  80        format('~n').                           % 200 with empty body
  81  ==
  82
  83@see    http://en.wikipedia.org/wiki/Cross-site_scripting for understanding
  84        Cross-site scripting.
  85@see    http://www.w3.org/TR/cors/ for understanding CORS
  86*/
  87
  88
  89%!  cors_enable is det.
  90%
  91%   Emit  the  HTTP  header   =|Access-Control-Allow-Origin|=  using
  92%   domains from the setting http:cors.  This   this  setting  is []
  93%   (default), nothing is written. This  predicate is typically used
  94%   for replying to API  HTTP-request  (e.g.,   replies  to  an AJAX
  95%   request that typically serve JSON or XML).
  96
  97cors_enable :-
  98    cors_enable_domain,
  99    !.
 100cors_enable.                            % CORS not enabled
 101
 102cors_enable_domain :-
 103    setting(http:cors, List),
 104    List \== [],
 105    !,
 106    format('Access-Control-Allow-Origin: ', []),
 107    write_domains(List),
 108    nl.
 109
 110write_domains([]).
 111write_domains([H|T]) :-
 112    write(H),
 113    (   T == []
 114    ->  true
 115    ;   write(' '),
 116        write_domains(T)
 117    ).
 118
 119%!  cors_enable(+Request, +Options) is det.
 120%
 121%   CORS reply to a _Preflight_ =OPTIONS=   request.  Request is the
 122%   HTTP request. Options provides:
 123%
 124%     - methods(+List)
 125%     List of supported HTTP methods.  The default is =GET=, only
 126%     allowing for read requests.
 127%     - headers(+List)
 128%     List of headers the client asks for and we allow.  The
 129%     default is to simply echo what has been requested for.
 130%
 131%   Both methods and headers may use   Prolog friendly syntax, e.g.,
 132%   =get= for a method and =content_type= for a header.
 133%
 134%   @see http://www.html5rocks.com/en/tutorials/cors/
 135
 136cors_enable(Request, Options) :-
 137    cors_enable_domain,
 138    !,
 139    option(methods(Methods), Options, [get]),
 140    cors_methods(Methods),
 141    (   option(headers(ReqHeaders), Options)
 142    ->  cors_request_headers(ReqHeaders)
 143    ;   option(access_control_request_headers(ReqHeader), Request)
 144    ->  format('Access-Control-Allow-Headers: ~w~n', [ReqHeader])
 145    ;   true
 146    ).
 147cors_enable(_, _).
 148
 149cors_methods([]) :- !.
 150cors_methods(Methods) :-
 151    format('Access-Control-Allow-Methods: '),
 152    write_methods(Methods),
 153    nl.
 154
 155write_methods([H|T]) :-
 156    upcase_atom(H, U),
 157    write(U),
 158    (   T == []
 159    ->  true
 160    ;   write(', '),
 161        write_methods(T)
 162    ).
 163
 164cors_request_headers([]) :- !.
 165cors_request_headers(ReqHeaders) :-
 166    phrase(field_names(ReqHeaders), String),
 167    format('Access-Control-Allow-Headers: ~s', String).
 168
 169
 170field_names([H|T]) -->
 171    http_header:field_name(H),
 172    (   {T==[]}
 173    ->  ""
 174    ;   ", ",
 175        field_names(T)
 176    ).