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 % +Request, +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 )