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 86CE4D35F for ; Thu, 1 Nov 2012 05:41:50 +0000 (UTC) Received: (qmail 8578 invoked by uid 500); 1 Nov 2012 05:41:50 -0000 Delivered-To: apmail-couchdb-dev-archive@couchdb.apache.org Received: (qmail 8401 invoked by uid 500); 1 Nov 2012 05:41:49 -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 8372 invoked by uid 99); 1 Nov 2012 05:41:48 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 01 Nov 2012 05:41:48 +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 (nike.apache.org: domain of bchesneau@gmail.com designates 209.85.210.180 as permitted sender) Received: from [209.85.210.180] (HELO mail-ia0-f180.google.com) (209.85.210.180) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 01 Nov 2012 05:41:38 +0000 Received: by mail-ia0-f180.google.com with SMTP id f6so1615613iag.11 for ; Wed, 31 Oct 2012 22:41:16 -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=tJdth2XKx2UkER7FiCvy3MYDMPKJn9LaAG1uKjcdwYk=; b=SsGJyTxmBL29cM2qtCZcscwm+7Xcw3GNsbfLMXLt3jnQM8vHsJ8065yxCA9BhQ4z5Z Oy++Nsd3LR9fuTyVqoRBQSjADcR1rd/J8JiWq6QocJLdWe7yCDPnP/OS+ExBGeNynm31 nBQWiztf+zEG/XoGfcJdB98/9UoO7zH0MrX4als3spTQNzAH3QiJgg5i+yV4Nbz7au4r WeJo9FwCxJlo7FM+STnmZBNIFedkvZdzpSJh1GuSFKXlyT5tOghbAd8On7L9dKcFzQLW 7M0Z8pIvKJBmEhhI7eP+CfnA8steFY0CALqCRUQKT6q0WGGyXqpVW37rU+PwFDZ0YjAm xdvA== MIME-Version: 1.0 Received: by 10.42.109.194 with SMTP id m2mr33212727icp.48.1351748476660; Wed, 31 Oct 2012 22:41:16 -0700 (PDT) Received: by 10.64.77.196 with HTTP; Wed, 31 Oct 2012 22:41:16 -0700 (PDT) In-Reply-To: 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:41:16 +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=20cf303dd33ec6ed7d04cd6877c3 X-Virus-Checked: Checked by ClamAV on apache.org --20cf303dd33ec6ed7d04cd6877c3 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable ok I will stop for now pushed 431_feature_cors (the removing of caps is wanted here and I will be happy to just update the jira title if someone really care) . Side note: Imo adding feature in the naming here doesn't give anything . Prefixing by feature/ at least would optimize queries for rapid eye looking or a bot going over the issues... On Thu, Nov 1, 2012 at 6:36 AM, Benoit Chesneau wrote= : > 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 th= e >> thread on branch naming that you started earlier today was that the bran= ch >> name should indicate whether it's a *feature* or a *bugfix*. In this ca= se >> 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 >> renamed >> > 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 namin= g >> >> 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 >> last >> >>>> 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 >> naming >> >>>>> 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/0777262= f >> >>>>>> Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/0777262= f >> >>>>>> >> >>>>>> 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 pag= e. >> >>>>>> ;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 cac= he >> >> entries >> >>>>>> allow_persistent_cookies =3D false ; set to true to allow persist= ent >> >>>>> 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 >> looked up >> >>>>>> ; in the authentication database (_users). These secrets are >> stored in >> >>>>>> @@ -224,7 +245,7 @@ socket_options =3D [{keepalive, true}, {nodel= ay, >> >>>>> 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 privat= e >> >>>>> keyfile is password protected. >> >>>>>> +; String containing the user's password. Only used if the privat= e >> >>>>> 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.h= rl >> >>>>>> +EXTRA_DIST =3D $(source_files) couch_db.hrl couch_js_functions.h= rl >> >>>>>> >> >>>>>> 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 wi= th >> >> 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=3DMochiReq}) >> >> -> >> >>>>>> 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(Req, >> >> []) >> >>>>> ++ 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, >> Headers, >> >>>>> 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}= =3DReq, >> >> Code, >> >>>>> Headers) -> >> >>>>>> log_request(Req, Code), >> >>>>>> couch_stats_collector:increment({httpd_status_codes, Code}), >> >>>>>> CookieHeader =3D couch_httpd_auth:cookie_auth_header(Req, Heade= rs), >> >>>>>> - 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, = Body) >> -> >> >>>>>> 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" >> BASIS, >> >>>>> WITHOUT >> >>>>>> +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implie= d. >> >> 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, >> "origins", >> >> []), >> >>>>>> + "\\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, >> "origins", >> >> []), >> >>>>>> + "\\s*,\\s*", >> >>>>>> + [trim, {return, list}]), >> >>>>>> + case AcceptedOrigins of >> >>>>>> + [] -> >> >>>>>> + >> >> handle_preflight_request(couch_util:to_list(Origin), >> >>>>>> + Host, MochiReq); >> >>>>>> + _ -> >> >>>>>> + case lists:member(Origin, AcceptedOrigins) o= f >> >>>>>> + 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, "header= s", >> >>>>>> + >> >> ?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) = of >> >>>>>> + 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 >> is 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 PreflightHeader= s0 >> ++ >> >>>>>> + >> >>>>> [{"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 = to >> >>>>>> %% match the Host header (or forwarded) against rules built again= st >> >>>>>> -%% 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 = db >> >>>>>> %% 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", P= ath, >> >>>>>> + Headers =3D mochiweb_headers:enter("x-couchdb-vhost-path", P= ath, >> >>>>>> 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 database= s >> >>>>> '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/2= 31_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" >> BASIS, >> >>>>> WITHOUT >> >>>>>> +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implie= d. >> >> 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", fals= e), >> >>>>>> + ok =3D couch_config:set("cors", "origins", "http://example.c= om >> ", >> >>>>> 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-Orig= in >> >>>>>> +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-Orig= in >> >>>>>> +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(), Heade= rs, >> >> 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(), Heade= rs, >> >>>>> 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. >> >>>>>> >> >>>>> >> >>>>> >> >>> >> >> >> >> > --20cf303dd33ec6ed7d04cd6877c3--