Return-Path: X-Original-To: apmail-couchdb-dev-archive@www.apache.org Delivered-To: apmail-couchdb-dev-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 4E15DD34E for ; Thu, 1 Nov 2012 05:36:34 +0000 (UTC) Received: (qmail 99085 invoked by uid 500); 1 Nov 2012 05:36:33 -0000 Delivered-To: apmail-couchdb-dev-archive@couchdb.apache.org Received: (qmail 98875 invoked by uid 500); 1 Nov 2012 05:36:33 -0000 Mailing-List: contact dev-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list dev@couchdb.apache.org Received: (qmail 98837 invoked by uid 99); 1 Nov 2012 05:36:32 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 01 Nov 2012 05:36:32 +0000 X-ASF-Spam-Status: No, hits=2.5 required=5.0 tests=FREEMAIL_REPLY,HTML_MESSAGE,NORMAL_HTTP_TO_IP,RCVD_IN_DNSWL_LOW,SPF_PASS X-Spam-Check-By: apache.org Received-SPF: pass (athena.apache.org: domain of bchesneau@gmail.com designates 209.85.223.180 as permitted sender) Received: from [209.85.223.180] (HELO mail-ie0-f180.google.com) (209.85.223.180) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 01 Nov 2012 05:36:27 +0000 Received: by mail-ie0-f180.google.com with SMTP id e10so3039468iej.11 for ; Wed, 31 Oct 2012 22:36:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:date:message-id:subject:from:to :content-type; bh=9FaLZVpoUxL4iEwpAbpK5AAKhtmc8OSrm8/WLQLetgQ=; b=q4kCitBp+IGREdC2raOv+Vp6AaLnsj2nHLreen+xrOu0jALNPSOkgs/P9ERiwTrOOi 2EXy56Hombjtrih7oIIyo61LvRn5zgiITaZRBOU8zhHQt8yr/23f5jm2Mohyp2sMovBA he7QY4bSe0mfCI3hJgT5PjTFZbxoooQieuD1R29o/8C8th3KNGwxuaFMQpaammBNcK/7 4eMAlizMxuuL6phEmghapLbBWdWMH2msnXo6t37r8UNDxxj1MJM+9dR1Nd4/4nHGVZzr 943CZHEpTqInHoX+EJP76UO1LpIyNhegq7hOeixdLLMx7C2G4RBggTyXPnyAEgmlO/iJ qN8Q== MIME-Version: 1.0 Received: by 10.50.77.230 with SMTP id v6mr169947igw.11.1351748166527; Wed, 31 Oct 2012 22:36:06 -0700 (PDT) Received: by 10.64.77.196 with HTTP; Wed, 31 Oct 2012 22:36:06 -0700 (PDT) In-Reply-To: <6BC31E10-AA09-4A2C-B6F1-F29DA2DB8290@apache.org> References: <20121031234336.4B0DB5154F@tyr.zones.apache.org> <57B93226-49D4-4F20-9991-AC8A50A7F39D@apache.org> <26DCFB09-C3A7-4596-8B5E-4E7E40B61BF4@apache.org> <6BC31E10-AA09-4A2C-B6F1-F29DA2DB8290@apache.org> Date: Thu, 1 Nov 2012 06:36:06 +0100 Message-ID: Subject: Re: git commit: handle CORS. fix #COUCHDB-431 From: Benoit Chesneau To: "dev@couchdb.apache.org" Content-Type: multipart/alternative; boundary=e89a8f3ba87f4aa9bd04cd686598 X-Virus-Checked: Checked by ClamAV on apache.org --e89a8f3ba87f4aa9bd04cd686598 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable that's naming is really awkward should be feature/431-cors (why keeping caps?) . Anyway changing it again. On Thu, Nov 1, 2012 at 6:33 AM, Adam Kocoloski wrote: > Benoit, sorry to keep nagging, but one of the specific conclusions of the > thread on branch naming that you started earlier today was that the branc= h > name should indicate whether it's a *feature* or a *bugfix*. In this cas= e > the syntax would be 431-feature-CORS. > > Adam > > On Nov 1, 2012, at 1:30 AM, Benoit Chesneau wrote: > > > The branch have been renamed to 431_cors. I'm not sure it will be renam= ed > > on github too. > > > > - benoit > > > > > > On Thu, Nov 1, 2012 at 5:34 AM, Paul Davis >wrote: > > > >> On Wed, Oct 31, 2012 at 8:14 PM, Adam Kocoloski > >> wrote: > >>> Right, the wiki page for this stuff is > >> http://wiki.apache.org/couchdb/Merge_Procedure which now reads > >>> > >>>> Please use the ticket number, the type of the branch, along with a > very > >> short descriptive phrase, for your branch name. > >>>> > >>>> If the ticket was COUCHDB-1234, and the ticket title was My Cool > >> Feature, your branch should be called 1234-feature-cool. If the issue > is a > >> bug and the branch includes the bug fix, it should be called > 1234-fix-cool. > >>> > >>> Perhaps we should kill this branch and re-upload to follow the naming > >> scheme? Cheers, > >>> > >> > >> I think there's a git syntax for renaming on a remote. > >> > >>> Adam > >>> > >>> On Oct 31, 2012, at 7:53 PM, Benoit Chesneau > >> wrote: > >>> > >>>> hrmmmm i thought it was ticketnumber_shortdescr.... I didn't read la= st > >>>> update of the wiki though .. > >>>> > >>>> - beno=EEt > >>>> > >>>> > >>>> On Thu, Nov 1, 2012 at 12:50 AM, Adam Kocoloski > >> wrote: > >>>> > >>>>> A minor thing -- didn't we just propose earlier today to use a nami= ng > >>>>> convention like 431-feature-CORS for these topic branches? > >>>>> > >>>>> Adam > >>>>> > >>>>> On Oct 31, 2012, at 7:43 PM, benoitc@apache.org wrote: > >>>>> > >>>>>> Updated Branches: > >>>>>> refs/heads/COUCHDB-431_cors [created] 0777262fa > >>>>>> > >>>>>> > >>>>>> handle CORS. fix #COUCHDB-431 > >>>>>> > >>>>>> This patch as support of CORS requests and preflights request as a > >> node > >>>>>> level. vhosts are supported > >>>>>> > >>>>>> > >>>>>> Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo > >>>>>> Commit: > >> http://git-wip-us.apache.org/repos/asf/couchdb/commit/0777262f > >>>>>> Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/0777262f > >>>>>> Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/0777262f > >>>>>> > >>>>>> Branch: refs/heads/COUCHDB-431_cors > >>>>>> Commit: 0777262fa291a79555ea23f2ff203d1ae7654547 > >>>>>> Parents: 88c52b2 > >>>>>> Author: benoitc > >>>>>> Authored: Thu Nov 1 00:41:00 2012 +0100 > >>>>>> Committer: benoitc > >>>>>> Committed: Thu Nov 1 00:41:00 2012 +0100 > >>>>>> > >>>>>> > ---------------------------------------------------------------------- > >>>>>> etc/couchdb/default.ini.tpl.in | 23 +++- > >>>>>> src/couchdb/Makefile.am | 4 +- > >>>>>> src/couchdb/couch_httpd.erl | 53 ++++++-- > >>>>>> src/couchdb/couch_httpd_cors.erl | 230 > >> ++++++++++++++++++++++++++++++++ > >>>>>> src/couchdb/couch_httpd_vhost.erl | 55 ++++---- > >>>>>> test/etap/231_cors.t | 230 > >> ++++++++++++++++++++++++++++++++ > >>>>>> 6 files changed, 553 insertions(+), 42 deletions(-) > >>>>>> > ---------------------------------------------------------------------- > >>>>>> > >>>>>> > >>>>>> > >>>>> > >> > http://git-wip-us.apache.org/repos/asf/couchdb/blob/0777262f/etc/couchdb/= default.ini.tpl.in > >>>>>> > ---------------------------------------------------------------------- > >>>>>> diff --git a/etc/couchdb/default.ini.tpl.in b/etc/couchdb/ > >>>>> default.ini.tpl.in > >>>>>> index 79ece5c..6a32f65 100644 > >>>>>> --- a/etc/couchdb/default.ini.tpl.in > >>>>>> +++ b/etc/couchdb/default.ini.tpl.in > >>>>>> @@ -49,6 +49,7 @@ allow_jsonp =3D false > >>>>>> ; For more socket options, consult Erlang's module 'inet' man page= . > >>>>>> ;socket_options =3D [{recbuf, 262144}, {sndbuf, 262144}, {nodelay, > >> true}] > >>>>>> log_max_chunk_size =3D 1000000 > >>>>>> +cors_enable =3D false > >>>>>> > >>>>>> [ssl] > >>>>>> port =3D 6984 > >>>>>> @@ -67,6 +68,26 @@ auth_cache_size =3D 50 ; size is number of cach= e > >> entries > >>>>>> allow_persistent_cookies =3D false ; set to true to allow persiste= nt > >>>>> cookies > >>>>>> iterations =3D 10000 ; iterations for password hashing > >>>>>> > >>>>>> +[cors] > >>>>>> +allows_credentials =3D false > >>>>>> +; List of origins separated by a comma > >>>>>> +;origins =3D > >>>>>> +; List of accepted headers separated by a comma > >>>>>> +; headers =3D > >>>>>> +; List of accepted methods > >>>>>> +; methods =3D > >>>>>> + > >>>>>> + > >>>>>> +; Configuration for a vhost > >>>>>> +:[cors:example.com] > >>>>>> +; allows_credentials =3D false > >>>>>> +; List of origins separated by a comma > >>>>>> +;origins =3D > >>>>>> +; List of accepted headers separated by a comma > >>>>>> +; headers =3D > >>>>>> +; List of accepted methods > >>>>>> +; methods =3D > >>>>>> + > >>>>>> [couch_httpd_oauth] > >>>>>> ; If set to 'true', oauth token and consumer secrets will be looke= d > up > >>>>>> ; in the authentication database (_users). These secrets are store= d > in > >>>>>> @@ -224,7 +245,7 @@ socket_options =3D [{keepalive, true}, {nodela= y, > >>>>> false}] > >>>>>> ;cert_file =3D /full/path/to/server_cert.pem > >>>>>> ; Path to file containing user's private PEM encoded key. > >>>>>> ;key_file =3D /full/path/to/server_key.pem > >>>>>> -; String containing the user's password. Only used if the private > >>>>> keyfile is password protected. > >>>>>> +; String containing the user's password. Only used if the private > >>>>> keyfile is password protected. > >>>>>> ;password =3D somepassword > >>>>>> ; Set to true to validate peer certificates. > >>>>>> verify_ssl_certificates =3D false > >>>>>> > >>>>>> > >>>>> > >> > http://git-wip-us.apache.org/repos/asf/couchdb/blob/0777262f/src/couchdb/= Makefile.am > >>>>>> > ---------------------------------------------------------------------- > >>>>>> diff --git a/src/couchdb/Makefile.am b/src/couchdb/Makefile.am > >>>>>> index 5705976..9fe19bc 100644 > >>>>>> --- a/src/couchdb/Makefile.am > >>>>>> +++ b/src/couchdb/Makefile.am > >>>>>> @@ -49,6 +49,7 @@ source_files =3D \ > >>>>>> couch_httpd.erl \ > >>>>>> couch_httpd_db.erl \ > >>>>>> couch_httpd_auth.erl \ > >>>>>> + couch_httpd_cors.erl \ > >>>>>> couch_httpd_oauth.erl \ > >>>>>> couch_httpd_external.erl \ > >>>>>> couch_httpd_misc_handlers.erl \ > >>>>>> @@ -79,7 +80,7 @@ source_files =3D \ > >>>>>> couch_work_queue.erl \ > >>>>>> json_stream_parse.erl > >>>>>> > >>>>>> -EXTRA_DIST =3D $(source_files) couch_db.hrl couch_js_functions.hr= l > >>>>>> +EXTRA_DIST =3D $(source_files) couch_db.hrl couch_js_functions.hr= l > >>>>>> > >>>>>> compiled_files =3D \ > >>>>>> couch.app \ > >>>>>> @@ -106,6 +107,7 @@ compiled_files =3D \ > >>>>>> couch_httpd_db.beam \ > >>>>>> couch_httpd_auth.beam \ > >>>>>> couch_httpd_oauth.beam \ > >>>>>> + couch_httpd_cors.beam \ > >>>>>> couch_httpd_proxy.beam \ > >>>>>> couch_httpd_external.beam \ > >>>>>> couch_httpd_misc_handlers.beam \ > >>>>>> > >>>>>> > >>>>> > >> > http://git-wip-us.apache.org/repos/asf/couchdb/blob/0777262f/src/couchdb/= couch_httpd.erl > >>>>>> > ---------------------------------------------------------------------- > >>>>>> diff --git a/src/couchdb/couch_httpd.erl > b/src/couchdb/couch_httpd.erl > >>>>>> index 45ceebc..6bba871 100644 > >>>>>> --- a/src/couchdb/couch_httpd.erl > >>>>>> +++ b/src/couchdb/couch_httpd.erl > >>>>>> @@ -275,7 +275,10 @@ handle_request_int(MochiReq, DefaultFun, > >>>>>> > >>>>>> % allow broken HTTP clients to fake a full method vocabulary wit= h > >> an > >>>>> X-HTTP-METHOD-OVERRIDE header > >>>>>> MethodOverride =3D > >>>>> MochiReq:get_primary_header_value("X-HTTP-Method-Override"), > >>>>>> - Method2 =3D case lists:member(MethodOverride, ["GET", "HEAD", > >> "POST", > >>>>> "PUT", "DELETE", "TRACE", "CONNECT", "COPY"]) of > >>>>>> + Method2 =3D case lists:member(MethodOverride, ["GET", "HEAD", > >> "POST", > >>>>>> + "PUT", "DELETE", > >>>>>> + "TRACE", > "CONNECT", > >>>>>> + "COPY"]) of > >>>>>> true -> > >>>>>> ?LOG_INFO("MethodOverride: ~s (real method was ~s)", > >>>>> [MethodOverride, Method1]), > >>>>>> case Method1 of > >>>>>> @@ -312,13 +315,19 @@ handle_request_int(MochiReq, DefaultFun, > >>>>>> HandlerFun =3D couch_util:dict_find(HandlerKey, UrlHandlers, > >>>>> DefaultFun), > >>>>>> {ok, AuthHandlers} =3D application:get_env(couch, auth_handlers)= , > >>>>>> > >>>>>> + ?LOG_INFO("fuck you ~p~n", [Method]), > >>>>>> {ok, Resp} =3D > >>>>>> try > >>>>>> - case authenticate_request(HttpReq, AuthHandlers) of > >>>>>> - #httpd{} =3D Req -> > >>>>>> - HandlerFun(Req); > >>>>>> - Response -> > >>>>>> - Response > >>>>>> + case couch_httpd_cors:is_preflight_request(HttpReq) of > >>>>>> + #httpd{} -> > >>>>>> + case authenticate_request(HttpReq, AuthHandlers) = of > >>>>>> + #httpd{} =3D Req -> > >>>>>> + HandlerFun(Req); > >>>>>> + Response -> > >>>>>> + Response > >>>>>> + end; > >>>>>> + Response -> > >>>>>> + Response > >>>>>> end > >>>>>> catch > >>>>>> throw:{http_head_abort, Resp0} -> > >>>>>> @@ -450,10 +459,13 @@ accepted_encodings(#httpd{mochi_req=3DMochiR= eq}) > >> -> > >>>>>> serve_file(Req, RelativePath, DocumentRoot) -> > >>>>>> serve_file(Req, RelativePath, DocumentRoot, []). > >>>>>> > >>>>>> -serve_file(#httpd{mochi_req=3DMochiReq}=3DReq, RelativePath, > >> DocumentRoot, > >>>>> ExtraHeaders) -> > >>>>>> +serve_file(#httpd{mochi_req=3DMochiReq}=3DReq, RelativePath, > >> DocumentRoot, > >>>>>> + ExtraHeaders) -> > >>>>>> log_request(Req, 200), > >>>>>> - {ok, MochiReq:serve_file(RelativePath, DocumentRoot, > >>>>>> - server_header() ++ couch_httpd_auth:cookie_auth_header(Re= q, > >> []) > >>>>> ++ ExtraHeaders)}. > >>>>>> + {ok, MochiReq:serve_file(RelativePath, DocumentRoot, > >>>>> server_header() ++ > >>>>>> + couch_httpd_cors:cors_headers(Req) += + > >>>>>> + > couch_httpd_auth:cookie_auth_header(Req, > >>>>> []) ++ > >>>>>> + ExtraHeaders)}. > >>>>>> > >>>>>> qs_value(Req, Key) -> > >>>>>> qs_value(Req, Key, undefined). > >>>>>> @@ -603,7 +615,10 @@ > log_request(#httpd{mochi_req=3DMochiReq,peer=3DPeer}, > >>>>> Code) -> > >>>>>> start_response_length(#httpd{mochi_req=3DMochiReq}=3DReq, Code, He= aders, > >>>>> Length) -> > >>>>>> log_request(Req, Code), > >>>>>> couch_stats_collector:increment({httpd_status_codes, Code}), > >>>>>> - Resp =3D MochiReq:start_response_length({Code, Headers ++ > >>>>> server_header() ++ couch_httpd_auth:cookie_auth_header(Req, Headers= ), > >>>>> Length}), > >>>>>> + Headers1 =3D Headers ++ server_header() ++ > >>>>>> + couch_httpd_auth:cookie_auth_header(Req, Headers) = ++ > >>>>>> + couch_httpd_cors:cors_headers(Req), > >>>>>> + Resp =3D MochiReq:start_response_length({Code, Headers1, > Length}), > >>>>>> case MochiReq:get(method) of > >>>>>> 'HEAD' -> throw({http_head_abort, Resp}); > >>>>>> _ -> ok > >>>>>> @@ -614,7 +629,8 @@ start_response(#httpd{mochi_req=3DMochiReq}=3D= Req, > >> Code, > >>>>> Headers) -> > >>>>>> log_request(Req, Code), > >>>>>> couch_stats_collector:increment({httpd_status_codes, Code}), > >>>>>> CookieHeader =3D couch_httpd_auth:cookie_auth_header(Req, Header= s), > >>>>>> - Headers2 =3D Headers ++ server_header() ++ CookieHeader, > >>>>>> + Headers2 =3D Headers ++ server_header() ++ CookieHeader ++ > >>>>>> + couch_httpd_cors:cors_headers(Req), > >>>>>> Resp =3D MochiReq:start_response({Code, Headers2}), > >>>>>> case MochiReq:get(method) of > >>>>>> 'HEAD' -> throw({http_head_abort, Resp}); > >>>>>> @@ -646,8 +662,11 @@ http_1_0_keep_alive(Req, Headers) -> > >>>>>> start_chunked_response(#httpd{mochi_req=3DMochiReq}=3DReq, Code, > Headers) > >> -> > >>>>>> log_request(Req, Code), > >>>>>> couch_stats_collector:increment({httpd_status_codes, Code}), > >>>>>> - Headers2 =3D http_1_0_keep_alive(MochiReq, Headers), > >>>>>> - Resp =3D MochiReq:respond({Code, Headers2 ++ server_header() = ++ > >>>>> couch_httpd_auth:cookie_auth_header(Req, Headers2), chunked}), > >>>>>> + Headers1 =3D http_1_0_keep_alive(MochiReq, Headers), > >>>>>> + Headers2 =3D Headers1 ++ server_header() ++ > >>>>>> + couch_httpd_auth:cookie_auth_header(Req, Headers1) > ++ > >>>>>> + couch_httpd_cors:cors_headers(Req), > >>>>>> + Resp =3D MochiReq:respond({Code, Headers2, chunked}), > >>>>>> case MochiReq:get(method) of > >>>>>> 'HEAD' -> throw({http_head_abort, Resp}); > >>>>>> _ -> ok > >>>>>> @@ -668,14 +687,18 @@ last_chunk(Resp) -> > >>>>>> send_response(#httpd{mochi_req=3DMochiReq}=3DReq, Code, Headers, B= ody) > -> > >>>>>> log_request(Req, Code), > >>>>>> couch_stats_collector:increment({httpd_status_codes, Code}), > >>>>>> - Headers2 =3D http_1_0_keep_alive(MochiReq, Headers), > >>>>>> + Headers1 =3D http_1_0_keep_alive(MochiReq, Headers), > >>>>>> if Code >=3D 500 -> > >>>>>> ?LOG_ERROR("httpd ~p error response:~n ~s", [Code, Body]); > >>>>>> Code >=3D 400 -> > >>>>>> ?LOG_DEBUG("httpd ~p error response:~n ~s", [Code, Body]); > >>>>>> true -> ok > >>>>>> end, > >>>>>> - {ok, MochiReq:respond({Code, Headers2 ++ server_header() ++ > >>>>> couch_httpd_auth:cookie_auth_header(Req, Headers2), Body})}. > >>>>>> + Headers2 =3D Headers1 ++ server_header() ++ > >>>>>> + couch_httpd_cors:cors_headers(Req) ++ > >>>>>> + couch_httpd_auth:cookie_auth_header(Req, Headers1)= , > >>>>>> + > >>>>>> + {ok, MochiReq:respond({Code, Headers2, Body})}. > >>>>>> > >>>>>> send_method_not_allowed(Req, Methods) -> > >>>>>> send_error(Req, 405, [{"Allow", Methods}], > >> <<"method_not_allowed">>, > >>>>> ?l2b("Only " ++ Methods ++ " allowed")). > >>>>>> > >>>>>> > >>>>> > >> > http://git-wip-us.apache.org/repos/asf/couchdb/blob/0777262f/src/couchdb/= couch_httpd_cors.erl > >>>>>> > ---------------------------------------------------------------------- > >>>>>> diff --git a/src/couchdb/couch_httpd_cors.erl > >>>>> b/src/couchdb/couch_httpd_cors.erl > >>>>>> new file mode 100644 > >>>>>> index 0000000..69f57ed > >>>>>> --- /dev/null > >>>>>> +++ b/src/couchdb/couch_httpd_cors.erl > >>>>>> @@ -0,0 +1,230 @@ > >>>>>> +% Licensed under the Apache License, Version 2.0 (the "License"); > you > >>>>> may not > >>>>>> +% use this file except in compliance with the License. You may > >> obtain a > >>>>> copy of > >>>>>> +% the License at > >>>>>> +% > >>>>>> +% http://www.apache.org/licenses/LICENSE-2.0 > >>>>>> +% > >>>>>> +% Unless required by applicable law or agreed to in writing, > software > >>>>>> +% distributed under the License is distributed on an "AS IS" BASI= S, > >>>>> WITHOUT > >>>>>> +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied= . > >> See > >>>>> the > >>>>>> +% License for the specific language governing permissions and > >>>>> limitations under > >>>>>> +% the License. > >>>>>> + > >>>>>> +%% @doc module to handle Cross-Origin Resource Sharing > >>>>>> +%% > >>>>>> +%% This module handles CROSS requests and preflight request for a > >>>>>> +%% couchdb Node. The config is done in the ini file. > >>>>>> + > >>>>>> + > >>>>>> +-module(couch_httpd_cors). > >>>>>> + > >>>>>> +-include("couch_db.hrl"). > >>>>>> + > >>>>>> +-export([is_preflight_request/1, cors_headers/1]). > >>>>>> + > >>>>>> +-define(SUPPORTED_HEADERS, "Accept, Accept-Language, Content-Type= ," > >> ++ > >>>>>> + "Expires, Last-Modified, Pragma, Origin, Content-Length," > ++ > >>>>>> + "If-Match, Destination, X-Requested-With, " ++ > >>>>>> + "X-Http-Method-Override, Content-Range"). > >>>>>> + > >>>>>> +-define(SUPPORTED_METHODS, "GET, HEAD, POST, PUT, DELETE," ++ > >>>>>> + "TRACE, CONNECT, COPY, OPTIONS"). > >>>>>> + > >>>>>> +is_preflight_request(#httpd{method=3DMethod}=3DReq) when Method /= =3D > >>>>> 'OPTIONS' -> > >>>>>> + Req; > >>>>>> +is_preflight_request(#httpd{mochi_req=3DMochiReq}=3DReq) -> > >>>>>> + case get_bool_config("httpd", "enable_cors", false) of > >>>>>> + true -> > >>>>>> + case preflight_request(MochiReq) of > >>>>>> + {ok, PreflightHeaders} -> > >>>>>> + couch_httpd:send_response(Req, 204, > >>>>> PreflightHeaders, <<>>); > >>>>>> + _ -> > >>>>>> + Req > >>>>>> + end; > >>>>>> + false -> > >>>>>> + Req > >>>>>> + end. > >>>>>> + > >>>>>> + > >>>>>> +cors_headers(#httpd{mochi_req=3DMochiReq}) -> > >>>>>> + Host =3D couch_httpd_vhost:host(MochiReq), > >>>>>> + case get_bool_config("httpd", "enable_cors", false) of > >>>>>> + true -> > >>>>>> + AcceptedOrigins =3D re:split(cors_config(Host, "origi= ns", > >> []), > >>>>>> + "\\s*,\\s*", > >>>>>> + [trim, {return, list}]), > >>>>>> + case MochiReq:get_header_value("Origin") of > >>>>>> + undefined -> > >>>>>> + []; > >>>>>> + <<"*">> -> > >>>>>> + handle_cors_headers("*", Host, > AcceptedOrigins); > >>>>>> + <<"null">> -> > >>>>>> + handle_cors_headers("*", Host, > AcceptedOrigins); > >>>>>> + Origin -> > >>>>>> + handle_cors_headers(couch_util:to_list(Origin= ), > >>>>>> + Host, AcceptedOrigins) > >>>>>> + end; > >>>>>> + false -> > >>>>>> + [] > >>>>>> + end. > >>>>>> + > >>>>>> +handle_cors_headers("*", _Host, _AcceptedOrigins) -> > >>>>>> + [{"Access-Control-Allow-Origin", "*"}]; > >>>>>> +handle_cors_headers(Origin, Host, []) -> > >>>>>> + case allows_credentials(Origin, Host) of > >>>>>> + true -> > >>>>>> + [{"Access-Control-Allow-Origin", Origin}, > >>>>>> + {"Access-Control-Allow-Credentials", "true"}]; > >>>>>> + false -> > >>>>>> + [{"Access-Control-Allow-Origin", Origin}] > >>>>>> + end; > >>>>>> +handle_cors_headers(Origin, Host, AcceptedOrigins) -> > >>>>>> + AllowsCredentials =3D allows_credentials(Origin, Host), > >>>>>> + case lists:member(Origin, AcceptedOrigins) of > >>>>>> + true when AllowsCredentials =3D:=3D true -> > >>>>>> + [{"Access-Control-Allow-Origin", Origin}, > >>>>>> + {"Access-Control-Allow-Credentials", "true"}]; > >>>>>> + true -> > >>>>>> + [{"Access-Control-Allow-Origin", Origin}]; > >>>>>> + _ -> > >>>>>> + [] > >>>>>> + end. > >>>>>> + > >>>>>> + > >>>>>> +preflight_request(MochiReq) -> > >>>>>> + Host =3D couch_httpd_vhost:host(MochiReq), > >>>>>> + case MochiReq:get_header_value("Origin") of > >>>>>> + undefined -> > >>>>>> + MochiReq; > >>>>>> + <<"*">> -> > >>>>>> + handle_preflight_request("*", Host, MochiReq); > >>>>>> + <<"null">> -> > >>>>>> + handle_preflight_request("*", Host, MochiReq); > >>>>>> + Origin -> > >>>>>> + AcceptedOrigins =3D re:split(cors_config(Host, "origi= ns", > >> []), > >>>>>> + "\\s*,\\s*", > >>>>>> + [trim, {return, list}]), > >>>>>> + case AcceptedOrigins of > >>>>>> + [] -> > >>>>>> + > >> handle_preflight_request(couch_util:to_list(Origin), > >>>>>> + Host, MochiReq); > >>>>>> + _ -> > >>>>>> + case lists:member(Origin, AcceptedOrigins) of > >>>>>> + true -> > >>>>>> + > >>>>> handle_preflight_request(couch_util:to_list(Origin), > >>>>>> + Host, > MochiReq); > >>>>>> + false -> > >>>>>> + false > >>>>>> + end > >>>>>> + end > >>>>>> + end. > >>>>>> + > >>>>>> +handle_preflight_request(Origin, Host, MochiReq) -> > >>>>>> + %% get supported methods > >>>>>> + SupportedMethods =3D split_list(cors_config(Host, "methods", > >>>>>> + ?SUPPORTED_METHODS)= ), > >>>>>> + > >>>>>> + % get supported headers > >>>>>> + AllSupportedHeaders =3D split_list(cors_config(Host, "headers= ", > >>>>>> + > >> ?SUPPORTED_HEADERS)), > >>>>>> + > >>>>>> + SupportedHeaders =3D [string:to_lower(H) || H <- > >> AllSupportedHeaders], > >>>>>> + > >>>>>> + % get max age > >>>>>> + MaxAge =3D cors_config(Host, "max_age", "12345"), > >>>>>> + > >>>>>> + PreflightHeaders0 =3D case allows_credentials(Origin, Host) o= f > >>>>>> + true -> > >>>>>> + [{"Access-Control-Allow-Origin", Origin}, > >>>>>> + {"Access-Control-Allow-Credentials", "true"}, > >>>>>> + {"Access-Control-Max-Age", MaxAge}, > >>>>>> + {"Access-Control-Allow-Methods", > >>>>> string:join(SupportedMethods, > >>>>>> + ", ")}]= ; > >>>>>> + false -> > >>>>>> + [{"Access-Control-Allow-Origin", Origin}, > >>>>>> + {"Access-Control-Max-Age", MaxAge}, > >>>>>> + {"Access-Control-Allow-Methods", > >>>>> string:join(SupportedMethods, > >>>>>> + ", ")}] > >>>>>> + end, > >>>>>> + > >>>>>> + case MochiReq:get_header_value("Access-Control-Request-Method= ") > >> of > >>>>>> + undefined -> > >>>>>> + {ok, PreflightHeaders0}; > >>>>>> + Method -> > >>>>>> + case lists:member(Method, SupportedMethods) of > >>>>>> + true -> > >>>>>> + % method ok , check headers > >>>>>> + AccessHeaders =3D MochiReq:get_header_value( > >>>>>> + "Access-Control-Request-Headers"), > >>>>>> + {FinalReqHeaders, ReqHeaders} =3D case > >> AccessHeaders > >>>>> of > >>>>>> + undefined -> {"", []}; > >>>>>> + Headers -> > >>>>>> + % transform header list in something = we > >>>>>> + % could check. make sure everything i= s > a > >>>>>> + % list > >>>>>> + RH =3D [string:to_lower(H) > >>>>>> + || H <- re:split(Headers, > ",\\s*", > >>>>>> + > >>>>> [{return,list},trim])], > >>>>>> + {Headers, RH} > >>>>>> + end, > >>>>>> + % check if headers are supported > >>>>>> + case ReqHeaders -- SupportedHeaders of > >>>>>> + [] -> > >>>>>> + PreflightHeaders =3D PreflightHeaders= 0 ++ > >>>>>> + > >>>>> [{"Access-Control-Allow-Headers", > >>>>>> + FinalReqHeaders}= ], > >>>>>> + {ok, PreflightHeaders}; > >>>>>> + _ -> > >>>>>> + false > >>>>>> + end; > >>>>>> + false -> > >>>>>> + false > >>>>>> + end > >>>>>> + end. > >>>>>> + > >>>>>> + > >>>>>> +allows_credentials("*", _Host) -> > >>>>>> + false; > >>>>>> +allows_credentials(_Origin, Host) -> > >>>>>> + Default =3D get_bool_config("cors", "allows_credentials", > >>>>>> + false), > >>>>>> + > >>>>>> + get_bool_config(cors_section(Host), "allows_credentials", > >>>>>> + Default). > >>>>>> + > >>>>>> + > >>>>>> +cors_config(Host, Key, Default) -> > >>>>>> + couch_config:get(cors_section(Host), Key, > >>>>>> + couch_config:get("cors", Key, Default)). > >>>>>> + > >>>>>> +cors_section(Host0) -> > >>>>>> + {Host, _Port} =3D split_host_port(Host0), > >>>>>> + "cors:" ++ Host. > >>>>>> + > >>>>>> +get_bool_config(Section, Key, Default) -> > >>>>>> + case couch_config:get(Section, Key) of > >>>>>> + undefined -> > >>>>>> + Default; > >>>>>> + "true" -> > >>>>>> + true; > >>>>>> + "false" -> > >>>>>> + false > >>>>>> + end. > >>>>>> + > >>>>>> +split_list(S) -> > >>>>>> + re:split(S, "\\s*,\\s*", [trim, {return, list}]). > >>>>>> + > >>>>>> +split_host_port(HostAsString) -> > >>>>>> + case string:rchr(HostAsString, $:) of > >>>>>> + 0 -> > >>>>>> + {HostAsString, '*'}; > >>>>>> + N -> > >>>>>> + HostPart =3D string:substr(HostAsString, 1, N-1), > >>>>>> + case (catch > >>>>> erlang:list_to_integer(string:substr(HostAsString, > >>>>>> + N+1, length(HostAsString)))) of > >>>>>> + {'EXIT', _} -> > >>>>>> + {HostAsString, '*'}; > >>>>>> + Port -> > >>>>>> + {HostPart, Port} > >>>>>> + end > >>>>>> + end. > >>>>>> > >>>>>> > >>>>> > >> > http://git-wip-us.apache.org/repos/asf/couchdb/blob/0777262f/src/couchdb/= couch_httpd_vhost.erl > >>>>>> > ---------------------------------------------------------------------- > >>>>>> diff --git a/src/couchdb/couch_httpd_vhost.erl > >>>>> b/src/couchdb/couch_httpd_vhost.erl > >>>>>> index 59f05ce..4c3ebfe 100644 > >>>>>> --- a/src/couchdb/couch_httpd_vhost.erl > >>>>>> +++ b/src/couchdb/couch_httpd_vhost.erl > >>>>>> @@ -15,7 +15,7 @@ > >>>>>> > >>>>>> -export([start_link/0, config_change/2, reload/0, get_state/0, > >>>>> dispatch_host/1]). > >>>>>> -export([urlsplit_netloc/2, redirect_to_vhost/2]). > >>>>>> - > >>>>>> +-export([host/1, split_host_port/1]). > >>>>>> > >>>>>> -export([init/1, handle_call/3, handle_cast/2, handle_info/2, > >>>>> terminate/2, code_change/3]). > >>>>>> > >>>>>> @@ -32,7 +32,7 @@ > >>>>>> %% doc the vhost manager. > >>>>>> %% This gen_server keep state of vhosts added to the ini and try t= o > >>>>>> %% match the Host header (or forwarded) against rules built agains= t > >>>>>> -%% vhost list. > >>>>>> +%% vhost list. > >>>>>> %% > >>>>>> %% Declaration of vhosts take place in the configuration file : > >>>>>> %% > >>>>>> @@ -51,7 +51,7 @@ > >>>>>> %% "*.db.example.com =3D /" will match all cname on top of d= b > >>>>>> %% examples to the root of the machine. > >>>>>> %% > >>>>>> -%% > >>>>>> +%% > >>>>>> %% Rewriting Hosts to path > >>>>>> %% ----------------------- > >>>>>> %% > >>>>>> @@ -75,7 +75,7 @@ > >>>>>> %% redirect_vhost_handler =3D {Module, Fun} > >>>>>> %% > >>>>>> %% The function take 2 args : the mochiweb request object and the > >> target > >>>>>> -%%% path. > >>>>>> +%%% path. > >>>>>> > >>>>>> start_link() -> > >>>>>> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). > >>>>>> @@ -98,15 +98,7 @@ dispatch_host(MochiReq) -> > >>>>>> {"/" ++ VPath, Query, Fragment} =3D > >>>>> mochiweb_util:urlsplit_path(MochiReq:get(raw_path)), > >>>>>> VPathParts =3D string:tokens(VPath, "/"), > >>>>>> > >>>>>> - XHost =3D couch_config:get("httpd", "x_forwarded_host", > >>>>> "X-Forwarded-Host"), > >>>>>> - VHost =3D case MochiReq:get_header_value(XHost) of > >>>>>> - undefined -> > >>>>>> - case MochiReq:get_header_value("Host") of > >>>>>> - undefined -> []; > >>>>>> - Value1 -> Value1 > >>>>>> - end; > >>>>>> - Value -> Value > >>>>>> - end, > >>>>>> + VHost =3D host(MochiReq), > >>>>>> {VHostParts, VhostPort} =3D split_host_port(VHost), > >>>>>> FinalMochiReq =3D case try_bind_vhost(VHosts, > >>>>> lists:reverse(VHostParts), > >>>>>> VhostPort, VPathParts) of > >>>>>> @@ -133,14 +125,14 @@ append_path("/"=3D_Target, "/"=3D_Path) -> > >>>>>> append_path(Target, Path) -> > >>>>>> Target ++ Path. > >>>>>> > >>>>>> -% default redirect vhost handler > >>>>>> +% default redirect vhost handler > >>>>>> redirect_to_vhost(MochiReq, VhostTarget) -> > >>>>>> Path =3D MochiReq:get(raw_path), > >>>>>> Target =3D append_path(VhostTarget, Path), > >>>>>> > >>>>>> ?LOG_DEBUG("Vhost Target: '~p'~n", [Target]), > >>>>>> > >>>>>> - Headers =3D mochiweb_headers:enter("x-couchdb-vhost-path", Pa= th, > >>>>>> + Headers =3D mochiweb_headers:enter("x-couchdb-vhost-path", Pa= th, > >>>>>> MochiReq:get(headers)), > >>>>>> > >>>>>> % build a new mochiweb request > >>>>>> @@ -154,7 +146,7 @@ redirect_to_vhost(MochiReq, VhostTarget) -> > >>>>>> MochiReq1. > >>>>>> > >>>>>> %% if so, then it will not be rewritten, but will run as a normal > >>>>> couchdb request. > >>>>>> -%* normally you'd use this for _uuids _utils and a few of the > others > >>>>> you want to > >>>>>> +%* normally you'd use this for _uuids _utils and a few of the > others > >>>>> you want to > >>>>>> %% keep available on vhosts. You can also use it to make databases > >>>>> 'global'. > >>>>>> vhost_global( VhostGlobals, MochiReq) -> > >>>>>> RawUri =3D MochiReq:get(raw_path), > >>>>>> @@ -175,14 +167,14 @@ try_bind_vhost([], _HostParts, _Port, > >> _PathParts) > >>>>> -> > >>>>>> try_bind_vhost([VhostSpec|Rest], HostParts, Port, PathParts) -> > >>>>>> {{VHostParts, VPort, VPath}, Path} =3D VhostSpec, > >>>>>> case bind_port(VPort, Port) of > >>>>>> - ok -> > >>>>>> + ok -> > >>>>>> case bind_vhost(lists:reverse(VHostParts), HostParts, []= ) > >> of > >>>>>> {ok, Bindings, Remainings} -> > >>>>>> case bind_path(VPath, PathParts) of > >>>>>> {ok, PathParts1} -> > >>>>>> Path1 =3D make_target(Path, Bindings, > >>>>> Remainings, []), > >>>>>> {make_path(Path1), make_path(PathParts1)= }; > >>>>>> - fail -> > >>>>>> + fail -> > >>>>>> try_bind_vhost(Rest, HostParts, Port, > >>>>>> PathParts) > >>>>>> end; > >>>>>> @@ -193,7 +185,7 @@ try_bind_vhost([VhostSpec|Rest], HostParts, > Port, > >>>>> PathParts) -> > >>>>>> > >>>>>> %% doc: build new patch from bindings. bindings are query args > >>>>>> %% (+ dynamic query rewritten if needed) and bindings found in > >>>>>> -%% bind_path step. > >>>>>> +%% bind_path step. > >>>>>> %% TODO: merge code with rewrite. But we need to make sure we are > >>>>>> %% in string here. > >>>>>> make_target([], _Bindings, _Remaining, Acc) -> > >>>>>> @@ -223,7 +215,7 @@ bind_vhost([],[], Bindings) -> {ok, Bindings, > []}; > >>>>>> bind_vhost([?MATCH_ALL], [], _Bindings) -> fail; > >>>>>> bind_vhost([?MATCH_ALL], Rest, Bindings) -> {ok, Bindings, Rest}; > >>>>>> bind_vhost([], _HostParts, _Bindings) -> fail; > >>>>>> -bind_vhost([{bind, Token}|Rest], [Match|RestHost], Bindings) -> > >>>>>> +bind_vhost([{bind, Token}|Rest], [Match|RestHost], Bindings) -> > >>>>>> bind_vhost(Rest, RestHost, [{{bind, Token}, Match}|Bindings]); > >>>>>> bind_vhost([Cname|Rest], [Cname|RestHost], Bindings) -> > >>>>>> bind_vhost(Rest, RestHost, Bindings); > >>>>>> @@ -243,6 +235,19 @@ bind_path(_, _) -> > >>>>>> > >>>>>> > >>>>>> %% create vhost list from ini > >>>>>> + > >>>>>> +host(MochiReq) -> > >>>>>> + XHost =3D couch_config:get("httpd", "x_forwarded_host", > >>>>>> + "X-Forwarded-Host"), > >>>>>> + case MochiReq:get_header_value(XHost) of > >>>>>> + undefined -> > >>>>>> + case MochiReq:get_header_value("Host") of > >>>>>> + undefined -> []; > >>>>>> + Value1 -> Value1 > >>>>>> + end; > >>>>>> + Value -> Value > >>>>>> + end. > >>>>>> + > >>>>>> make_vhosts() -> > >>>>>> Vhosts =3D lists:foldl(fun > >>>>>> ({_, ""}, Acc) -> > >>>>>> @@ -267,15 +272,15 @@ parse_vhost(Vhost) -> > >>>>>> H1 =3D make_spec(H, []), > >>>>>> {H1, P, string:tokens(Path, "/")} > >>>>>> end. > >>>>>> - > >>>>>> + > >>>>>> > >>>>>> split_host_port(HostAsString) -> > >>>>>> case string:rchr(HostAsString, $:) of > >>>>>> 0 -> > >>>>>> {split_host(HostAsString), '*'}; > >>>>>> N -> > >>>>>> - HostPart =3D string:substr(HostAsString, 1, N-1), > >>>>>> - case (catch > >>>>> erlang:list_to_integer(string:substr(HostAsString, > >>>>>> + HostPart =3D string:substr(HostAsString, 1, N-1), > >>>>>> + case (catch > >>>>> erlang:list_to_integer(string:substr(HostAsString, > >>>>>> N+1, length(HostAsString)))) of > >>>>>> {'EXIT', _} -> > >>>>>> {split_host(HostAsString), '*'}; > >>>>>> @@ -303,7 +308,7 @@ make_spec([P|R], Acc) -> > >>>>>> > >>>>>> > >>>>>> parse_var(P) -> > >>>>>> - case P of > >>>>>> + case P of > >>>>>> ":" ++ Var -> > >>>>>> {bind, Var}; > >>>>>> _ -> P > >>>>>> @@ -323,7 +328,7 @@ make_path(Parts) -> > >>>>>> > >>>>>> init(_) -> > >>>>>> ok =3D couch_config:register(fun ?MODULE:config_change/2), > >>>>>> - > >>>>>> + > >>>>>> %% load configuration > >>>>>> {VHostGlobals, VHosts, Fun} =3D load_conf(), > >>>>>> State =3D #vhosts_state{ > >>>>>> > >>>>>> > >>>>> > >> > http://git-wip-us.apache.org/repos/asf/couchdb/blob/0777262f/test/etap/23= 1_cors.t > >>>>>> > ---------------------------------------------------------------------- > >>>>>> diff --git a/test/etap/231_cors.t b/test/etap/231_cors.t > >>>>>> new file mode 100644 > >>>>>> index 0000000..72fc3df > >>>>>> --- /dev/null > >>>>>> +++ b/test/etap/231_cors.t > >>>>>> @@ -0,0 +1,230 @@ > >>>>>> +#!/usr/bin/env escript > >>>>>> +%% -*- erlang -*- > >>>>>> + > >>>>>> +% Licensed under the Apache License, Version 2.0 (the "License"); > you > >>>>> may not > >>>>>> +% use this file except in compliance with the License. You may > >> obtain a > >>>>> copy of > >>>>>> +% the License at > >>>>>> +% > >>>>>> +% http://www.apache.org/licenses/LICENSE-2.0 > >>>>>> +% > >>>>>> +% Unless required by applicable law or agreed to in writing, > software > >>>>>> +% distributed under the License is distributed on an "AS IS" BASI= S, > >>>>> WITHOUT > >>>>>> +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied= . > >> See > >>>>> the > >>>>>> +% License for the specific language governing permissions and > >>>>> limitations under > >>>>>> +% the License. > >>>>>> + > >>>>>> +-record(user_ctx, { > >>>>>> + name =3D null, > >>>>>> + roles =3D [], > >>>>>> + handler > >>>>>> +}). > >>>>>> + > >>>>>> + > >>>>>> +-define(SUPPORTED_METHODS, "GET, HEAD, POST, PUT, DELETE, TRACE, > >>>>> CONNECT, COPY, OPTIONS"). > >>>>>> +server() -> > >>>>>> + lists:concat([ > >>>>>> + "http://127.0.0.1:", > >>>>>> + mochiweb_socket_server:get(couch_httpd, port), > >>>>>> + "/" > >>>>>> + ]). > >>>>>> + > >>>>>> + > >>>>>> +main(_) -> > >>>>>> + test_util:init_code_path(), > >>>>>> + > >>>>>> + etap:plan(11), > >>>>>> + case (catch test()) of > >>>>>> + ok -> > >>>>>> + etap:end_tests(); > >>>>>> + Other -> > >>>>>> + etap:diag(io_lib:format("Test died abnormally: ~p", > >>>>> [Other])), > >>>>>> + etap:bail(Other) > >>>>>> + end, > >>>>>> + ok. > >>>>>> + > >>>>>> +dbname() -> "etap-test-db". > >>>>>> +dbname1() -> "etap-test-db1". > >>>>>> +dbname2() -> "etap-test-db2". > >>>>>> + > >>>>>> +admin_user_ctx() -> {user_ctx, #user_ctx{roles=3D[<<"_admin">>]}}= . > >>>>>> + > >>>>>> +set_admin_password(UserName, Password) -> > >>>>>> + Salt =3D binary_to_list(couch_uuids:random()), > >>>>>> + Hashed =3D couch_util:to_hex(crypto:sha(Password ++ Salt)), > >>>>>> + couch_config:set("admins", UserName, > >>>>>> + "-hashed-" ++ Hashed ++ "," ++ Salt, false). > >>>>>> + > >>>>>> +test() -> > >>>>>> + > >>>>>> + ibrowse:start(), > >>>>>> + crypto:start(), > >>>>>> + > >>>>>> + %% launch couchdb > >>>>>> + couch_server_sup:start_link(test_util:config_files()), > >>>>>> + > >>>>>> + %% initialize db > >>>>>> + timer:sleep(1000), > >>>>>> + couch_server:delete(list_to_binary(dbname()), > >> [admin_user_ctx()]), > >>>>>> + couch_server:delete(list_to_binary(dbname1()), > >> [admin_user_ctx()]), > >>>>>> + couch_server:delete(list_to_binary(dbname2()), > >> [admin_user_ctx()]), > >>>>>> + {ok, Db} =3D couch_db:create(list_to_binary(dbname()), > >>>>> [admin_user_ctx()]), > >>>>>> + {ok, Db1} =3D couch_db:create(list_to_binary(dbname1()), > >>>>> [admin_user_ctx()]), > >>>>>> + {ok, Db2} =3D couch_db:create(list_to_binary(dbname2()), > >>>>> [admin_user_ctx()]), > >>>>>> + > >>>>>> + % CORS is disabled by default > >>>>>> + test_no_headers_server(), > >>>>>> + test_no_headers_db(), > >>>>>> + > >>>>>> + % Now enable CORS > >>>>>> + ok =3D couch_config:set("httpd", "enable_cors", "true", false= ), > >>>>>> + ok =3D couch_config:set("cors", "origins", "http://example.co= m", > >>>>> false), > >>>>>> + > >>>>>> + %% do tests > >>>>>> + test_incorrect_origin_simple_request(), > >>>>>> + test_incorrect_origin_preflight_request(), > >>>>>> + > >>>>>> + test_preflight_request(), > >>>>>> + test_db_request(), > >>>>>> + test_db_preflight_request(), > >>>>>> + test_db_origin_request(), > >>>>>> + test_db1_origin_request(), > >>>>>> + > >>>>>> + %% do tests with auth > >>>>>> + ok =3D set_admin_password("test", "test"), > >>>>>> + > >>>>>> + test_db_preflight_auth_request(), > >>>>>> + test_db_origin_auth_request(), > >>>>>> + > >>>>>> + %% restart boilerplate > >>>>>> + catch couch_db:close(Db), > >>>>>> + catch couch_db:close(Db1), > >>>>>> + catch couch_db:close(Db2), > >>>>>> + > >>>>>> + couch_server:delete(list_to_binary(dbname()), > >> [admin_user_ctx()]), > >>>>>> + couch_server:delete(list_to_binary(dbname1()), > >> [admin_user_ctx()]), > >>>>>> + couch_server:delete(list_to_binary(dbname2()), > >> [admin_user_ctx()]), > >>>>>> + > >>>>>> + timer:sleep(3000), > >>>>>> + couch_server_sup:stop(), > >>>>>> + ok. > >>>>>> + > >>>>>> +%% Cors is disabled, should not return Access-Control-Allow-Origi= n > >>>>>> +test_no_headers_server() -> > >>>>>> + Headers =3D [{"Origin", "http://127.0.0.1"}], > >>>>>> + {ok, _, Resp, _} =3D ibrowse:send_req(server(), Headers, get, > []), > >>>>>> + etap:is(proplists:get_value("Access-Control-Allow-Origin", > Resp), > >>>>>> + undefined, "No CORS Headers when disabled"). > >>>>>> + > >>>>>> +%% Cors is disabled, should not return Access-Control-Allow-Origi= n > >>>>>> +test_no_headers_db() -> > >>>>>> + Headers =3D [{"Origin", "http://127.0.0.1"}], > >>>>>> + Url =3D server() ++ "etap-test-db", > >>>>>> + {ok, _, Resp, _} =3D ibrowse:send_req(Url, Headers, get, []), > >>>>>> + etap:is(proplists:get_value("Access-Control-Allow-Origin", > Resp), > >>>>>> + undefined, "No CORS Headers when disabled"). > >>>>>> + > >>>>>> +test_incorrect_origin_simple_request() -> > >>>>>> + Headers =3D [{"Origin", "http://127.0.0.1"}], > >>>>>> + {ok, _, RespHeaders, _} =3D ibrowse:send_req(server(), Header= s, > >> get, > >>>>> []), > >>>>>> + etap:is(proplists:get_value("Access-Control-Allow-Origin", > >>>>> RespHeaders), > >>>>>> + undefined, > >>>>>> + "Specified invalid origin, no Access"). > >>>>>> + > >>>>>> +test_incorrect_origin_preflight_request() -> > >>>>>> + Headers =3D [{"Origin", "http://127.0.0.1"}, > >>>>>> + {"Access-Control-Request-Method", "GET"}], > >>>>>> + {ok, _, RespHeaders, _} =3D ibrowse:send_req(server(), Header= s, > >>>>> options, []), > >>>>>> + etap:is(proplists:get_value("Access-Control-Allow-Origin", > >>>>> RespHeaders), > >>>>>> + undefined, > >>>>>> + "invalid origin"). > >>>>>> + > >>>>>> +test_preflight_request() -> > >>>>>> + Headers =3D [{"Origin", "http://example.com"}, > >>>>>> + {"Access-Control-Request-Method", "GET"}], > >>>>>> + case ibrowse:send_req(server(), Headers, options, []) of > >>>>>> + {ok, _, RespHeaders, _} -> > >>>>>> + etap:is(proplists:get_value("Access-Control-Allow-Methods= ", > >>>>> RespHeaders), > >>>>>> + ?SUPPORTED_METHODS, > >>>>>> + "test_preflight_request Access-Control-Allow-Methods > >> ok"); > >>>>>> + _ -> > >>>>>> + etap:is(false, true, "ibrowse failed") > >>>>>> + end. > >>>>>> + > >>>>>> +test_db_request() -> > >>>>>> + Headers =3D [{"Origin", "http://example.com"}], > >>>>>> + Url =3D server() ++ "etap-test-db", > >>>>>> + case ibrowse:send_req(Url, Headers, get, []) of > >>>>>> + {ok, _, RespHeaders, _Body} -> > >>>>>> + etap:is(proplists:get_value("Access-Control-Allow-Origin"= , > >>>>> RespHeaders), > >>>>>> + "http://example.com", > >>>>>> + "db Access-Control-Allow-Origin ok"); > >>>>>> + _ -> > >>>>>> + etap:is(false, true, "ibrowse failed") > >>>>>> + end. > >>>>>> + > >>>>>> +test_db_preflight_request() -> > >>>>>> + Url =3D server() ++ "etap-test-db", > >>>>>> + Headers =3D [{"Origin", "http://example.com"}, > >>>>>> + {"Access-Control-Request-Method", "GET"}], > >>>>>> + case ibrowse:send_req(Url, Headers, options, []) of > >>>>>> + {ok, _, RespHeaders, _} -> > >>>>>> + etap:is(proplists:get_value("Access-Control-Allow-Methods= ", > >>>>> RespHeaders), > >>>>>> + ?SUPPORTED_METHODS, > >>>>>> + "db Access-Control-Allow-Methods ok"); > >>>>>> + _ -> > >>>>>> + etap:is(false, true, "ibrowse failed") > >>>>>> + end. > >>>>>> + > >>>>>> + > >>>>>> +test_db_origin_request() -> > >>>>>> + Headers =3D [{"Origin", "http://example.com"}], > >>>>>> + Url =3D server() ++ "etap-test-db", > >>>>>> + case ibrowse:send_req(Url, Headers, get, []) of > >>>>>> + {ok, _, RespHeaders, _Body} -> > >>>>>> + etap:is(proplists:get_value("Access-Control-Allow-Origin"= , > >>>>> RespHeaders), > >>>>>> + "http://example.com", > >>>>>> + "db origin ok"); > >>>>>> + _ -> > >>>>>> + etap:is(false, true, "ibrowse failed") > >>>>>> + end. > >>>>>> + > >>>>>> +test_db1_origin_request() -> > >>>>>> + Headers =3D [{"Origin", "http://example.com"}], > >>>>>> + Url =3D server() ++ "etap-test-db1", > >>>>>> + case ibrowse:send_req(Url, Headers, get, [], [{host_header, " > >>>>> example.com"}]) of > >>>>>> + {ok, _, RespHeaders, _Body} -> > >>>>>> + etap:is(proplists:get_value("Access-Control-Allow-Origin"= , > >>>>> RespHeaders), > >>>>>> + "http://example.com", > >>>>>> + "db origin ok"); > >>>>>> + _Else -> > >>>>>> + io:format("else ~p~n", [_Else]), > >>>>>> + etap:is(false, true, "ibrowse failed") > >>>>>> + end. > >>>>>> + > >>>>>> +test_db_preflight_auth_request() -> > >>>>>> + Url =3D server() ++ "etap-test-db2", > >>>>>> + Headers =3D [{"Origin", "http://example.com"}, > >>>>>> + {"Access-Control-Request-Method", "GET"}], > >>>>>> + case ibrowse:send_req(Url, Headers, options, []) of > >>>>>> + {ok, _Status, RespHeaders, _} -> > >>>>>> + etap:is(proplists:get_value("Access-Control-Allow-Methods= ", > >>>>> RespHeaders), > >>>>>> + ?SUPPORTED_METHODS, > >>>>>> + "db Access-Control-Allow-Methods ok"); > >>>>>> + _ -> > >>>>>> + etap:is(false, true, "ibrowse failed") > >>>>>> + end. > >>>>>> + > >>>>>> + > >>>>>> +test_db_origin_auth_request() -> > >>>>>> + Headers =3D [{"Origin", "http://example.com"}], > >>>>>> + Url =3D server() ++ "etap-test-db2", > >>>>>> + > >>>>>> + case ibrowse:send_req(Url, Headers, get, [], > >>>>>> + [{basic_auth, {"test", "test"}}]) of > >>>>>> + {ok, _, RespHeaders, _Body} -> > >>>>>> + etap:is(proplists:get_value("Access-Control-Allow-Origin"= , > >>>>> RespHeaders), > >>>>>> + "http://example.com", > >>>>>> + "db origin ok"); > >>>>>> + _ -> > >>>>>> + etap:is(false, true, "ibrowse failed") > >>>>>> + end. > >>>>>> > >>>>> > >>>>> > >>> > >> > > --e89a8f3ba87f4aa9bd04cd686598--