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 branch name 
should indicate whether it's a *feature* or a *bugfix*.  In this case the 
syntax would be 431-feature-CORS.

Adam

On Nov 1, 2012, at 1:30 AM, Benoit Chesneau <[email protected]> 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 <[email protected]>wrote:
> 
>> On Wed, Oct 31, 2012 at 8:14 PM, Adam Kocoloski <[email protected]>
>> 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 <[email protected]>
>> wrote:
>>> 
>>>> hrmmmm i thought it was ticketnumber_shortdescr.... I didn't read last
>>>> update of the wiki though ..
>>>> 
>>>> - benoƮt
>>>> 
>>>> 
>>>> On Thu, Nov 1, 2012 at 12:50 AM, Adam Kocoloski <[email protected]>
>> 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, [email protected] 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 <[email protected]>
>>>>>> Authored: Thu Nov 1 00:41:00 2012 +0100
>>>>>> Committer: benoitc <[email protected]>
>>>>>> 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 = false
>>>>>> ; For more socket options, consult Erlang's module 'inet' man page.
>>>>>> ;socket_options = [{recbuf, 262144}, {sndbuf, 262144}, {nodelay,
>> true}]
>>>>>> log_max_chunk_size = 1000000
>>>>>> +cors_enable = false
>>>>>> 
>>>>>> [ssl]
>>>>>> port = 6984
>>>>>> @@ -67,6 +68,26 @@ auth_cache_size = 50 ; size is number of cache
>> entries
>>>>>> allow_persistent_cookies = false ; set to true to allow persistent
>>>>> cookies
>>>>>> iterations = 10000 ; iterations for password hashing
>>>>>> 
>>>>>> +[cors]
>>>>>> +allows_credentials = false
>>>>>> +; List of origins separated by a comma
>>>>>> +;origins =
>>>>>> +; List of accepted headers separated by a comma
>>>>>> +; headers =
>>>>>> +; List of accepted methods
>>>>>> +; methods =
>>>>>> +
>>>>>> +
>>>>>> +; Configuration for a vhost
>>>>>> +:[cors:example.com]
>>>>>> +; allows_credentials = false
>>>>>> +; List of origins separated by a comma
>>>>>> +;origins =
>>>>>> +; List of accepted headers separated by a comma
>>>>>> +; headers =
>>>>>> +; List of accepted methods
>>>>>> +; methods =
>>>>>> +
>>>>>> [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 = [{keepalive, true}, {nodelay,
>>>>> false}]
>>>>>> ;cert_file = /full/path/to/server_cert.pem
>>>>>> ; Path to file containing user's private PEM encoded key.
>>>>>> ;key_file = /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 = somepassword
>>>>>> ; Set to true to validate peer certificates.
>>>>>> verify_ssl_certificates = 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 = \
>>>>>>   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 = \
>>>>>>   couch_work_queue.erl \
>>>>>>   json_stream_parse.erl
>>>>>> 
>>>>>> -EXTRA_DIST = $(source_files) couch_db.hrl couch_js_functions.hrl
>>>>>> +EXTRA_DIST = $(source_files) couch_db.hrl couch_js_functions.hrl
>>>>>> 
>>>>>> compiled_files = \
>>>>>>   couch.app \
>>>>>> @@ -106,6 +107,7 @@ compiled_files = \
>>>>>>   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 with
>> an
>>>>> X-HTTP-METHOD-OVERRIDE header
>>>>>>   MethodOverride =
>>>>> MochiReq:get_primary_header_value("X-HTTP-Method-Override"),
>>>>>> -    Method2 = case lists:member(MethodOverride, ["GET", "HEAD",
>> "POST",
>>>>> "PUT", "DELETE", "TRACE", "CONNECT", "COPY"]) of
>>>>>> +    Method2 = 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 = couch_util:dict_find(HandlerKey, UrlHandlers,
>>>>> DefaultFun),
>>>>>>   {ok, AuthHandlers} = application:get_env(couch, auth_handlers),
>>>>>> 
>>>>>> +    ?LOG_INFO("fuck you ~p~n", [Method]),
>>>>>>   {ok, Resp} =
>>>>>>   try
>>>>>> -        case authenticate_request(HttpReq, AuthHandlers) of
>>>>>> -        #httpd{} = Req ->
>>>>>> -            HandlerFun(Req);
>>>>>> -        Response ->
>>>>>> -            Response
>>>>>> +        case couch_httpd_cors:is_preflight_request(HttpReq) of
>>>>>> +            #httpd{} ->
>>>>>> +                case authenticate_request(HttpReq, AuthHandlers) of
>>>>>> +                #httpd{} = Req ->
>>>>>> +                    HandlerFun(Req);
>>>>>> +                Response ->
>>>>>> +                    Response
>>>>>> +                end;
>>>>>> +            Response ->
>>>>>> +                Response
>>>>>>       end
>>>>>>   catch
>>>>>>       throw:{http_head_abort, Resp0} ->
>>>>>> @@ -450,10 +459,13 @@ accepted_encodings(#httpd{mochi_req=MochiReq})
>> ->
>>>>>> serve_file(Req, RelativePath, DocumentRoot) ->
>>>>>>   serve_file(Req, RelativePath, DocumentRoot, []).
>>>>>> 
>>>>>> -serve_file(#httpd{mochi_req=MochiReq}=Req, RelativePath,
>> DocumentRoot,
>>>>> ExtraHeaders) ->
>>>>>> +serve_file(#httpd{mochi_req=MochiReq}=Req, 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=MochiReq,peer=Peer},
>>>>> Code) ->
>>>>>> start_response_length(#httpd{mochi_req=MochiReq}=Req, Code, Headers,
>>>>> Length) ->
>>>>>>   log_request(Req, Code),
>>>>>>   couch_stats_collector:increment({httpd_status_codes, Code}),
>>>>>> -    Resp = MochiReq:start_response_length({Code, Headers ++
>>>>> server_header() ++ couch_httpd_auth:cookie_auth_header(Req, Headers),
>>>>> Length}),
>>>>>> +    Headers1 = Headers ++ server_header() ++
>>>>>> +               couch_httpd_auth:cookie_auth_header(Req, Headers) ++
>>>>>> +               couch_httpd_cors:cors_headers(Req),
>>>>>> +    Resp = 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=MochiReq}=Req,
>> Code,
>>>>> Headers) ->
>>>>>>   log_request(Req, Code),
>>>>>>   couch_stats_collector:increment({httpd_status_codes, Code}),
>>>>>>   CookieHeader = couch_httpd_auth:cookie_auth_header(Req, Headers),
>>>>>> -    Headers2 = Headers ++ server_header() ++ CookieHeader,
>>>>>> +    Headers2 = Headers ++ server_header() ++ CookieHeader ++
>>>>>> +               couch_httpd_cors:cors_headers(Req),
>>>>>>   Resp = 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=MochiReq}=Req, Code, Headers)
>> ->
>>>>>>   log_request(Req, Code),
>>>>>>   couch_stats_collector:increment({httpd_status_codes, Code}),
>>>>>> -    Headers2 = http_1_0_keep_alive(MochiReq, Headers),
>>>>>> -    Resp = MochiReq:respond({Code, Headers2 ++ server_header() ++
>>>>> couch_httpd_auth:cookie_auth_header(Req, Headers2), chunked}),
>>>>>> +    Headers1 = http_1_0_keep_alive(MochiReq, Headers),
>>>>>> +    Headers2 = Headers1 ++ server_header() ++
>>>>>> +               couch_httpd_auth:cookie_auth_header(Req, Headers1) ++
>>>>>> +               couch_httpd_cors:cors_headers(Req),
>>>>>> +    Resp = 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=MochiReq}=Req, Code, Headers, Body) ->
>>>>>>   log_request(Req, Code),
>>>>>>   couch_stats_collector:increment({httpd_status_codes, Code}),
>>>>>> -    Headers2 = http_1_0_keep_alive(MochiReq, Headers),
>>>>>> +    Headers1 = http_1_0_keep_alive(MochiReq, Headers),
>>>>>>   if Code >= 500 ->
>>>>>>       ?LOG_ERROR("httpd ~p error response:~n ~s", [Code, Body]);
>>>>>>   Code >= 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 = 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 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=Method}=Req) when Method /=
>>>>> 'OPTIONS' ->
>>>>>> +    Req;
>>>>>> +is_preflight_request(#httpd{mochi_req=MochiReq}=Req) ->
>>>>>> +    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=MochiReq}) ->
>>>>>> +    Host = couch_httpd_vhost:host(MochiReq),
>>>>>> +    case get_bool_config("httpd", "enable_cors", false) of
>>>>>> +        true ->
>>>>>> +            AcceptedOrigins = 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 = allows_credentials(Origin, Host),
>>>>>> +    case lists:member(Origin, AcceptedOrigins) of
>>>>>> +        true when AllowsCredentials =:= true ->
>>>>>> +            [{"Access-Control-Allow-Origin", Origin},
>>>>>> +             {"Access-Control-Allow-Credentials", "true"}];
>>>>>> +        true ->
>>>>>> +            [{"Access-Control-Allow-Origin", Origin}];
>>>>>> +        _ ->
>>>>>> +            []
>>>>>> +    end.
>>>>>> +
>>>>>> +
>>>>>> +preflight_request(MochiReq) ->
>>>>>> +    Host = 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 = 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) 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 = split_list(cors_config(Host, "methods",
>>>>>> +                                              ?SUPPORTED_METHODS)),
>>>>>> +
>>>>>> +    % get supported headers
>>>>>> +    AllSupportedHeaders = split_list(cors_config(Host, "headers",
>>>>>> +
>> ?SUPPORTED_HEADERS)),
>>>>>> +
>>>>>> +    SupportedHeaders = [string:to_lower(H) || H <-
>> AllSupportedHeaders],
>>>>>> +
>>>>>> +    % get max age
>>>>>> +    MaxAge = cors_config(Host, "max_age", "12345"),
>>>>>> +
>>>>>> +    PreflightHeaders0 = 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 = MochiReq:get_header_value(
>>>>>> +                            "Access-Control-Request-Headers"),
>>>>>> +                    {FinalReqHeaders, ReqHeaders} = case
>> AccessHeaders
>>>>> of
>>>>>> +                        undefined -> {"", []};
>>>>>> +                        Headers ->
>>>>>> +                            % transform header list in something we
>>>>>> +                            % could check. make sure everything is a
>>>>>> +                            % list
>>>>>> +                            RH = [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 = PreflightHeaders0 ++
>>>>>> +
>>>>> [{"Access-Control-Allow-Headers",
>>>>>> +                                                 FinalReqHeaders}],
>>>>>> +                            {ok, PreflightHeaders};
>>>>>> +                        _ ->
>>>>>> +                            false
>>>>>> +                    end;
>>>>>> +                false ->
>>>>>> +                    false
>>>>>> +            end
>>>>>> +    end.
>>>>>> +
>>>>>> +
>>>>>> +allows_credentials("*", _Host) ->
>>>>>> +    false;
>>>>>> +allows_credentials(_Origin, Host) ->
>>>>>> +    Default = 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} = 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 = 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 against
>>>>>> -%% vhost list.
>>>>>> +%% vhost list.
>>>>>> %%
>>>>>> %% Declaration of vhosts take place in the configuration file :
>>>>>> %%
>>>>>> @@ -51,7 +51,7 @@
>>>>>> %%      "*.db.example.com = /"  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 = {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} =
>>>>> mochiweb_util:urlsplit_path(MochiReq:get(raw_path)),
>>>>>>   VPathParts =  string:tokens(VPath, "/"),
>>>>>> 
>>>>>> -    XHost = couch_config:get("httpd", "x_forwarded_host",
>>>>> "X-Forwarded-Host"),
>>>>>> -    VHost = case MochiReq:get_header_value(XHost) of
>>>>>> -        undefined ->
>>>>>> -            case MochiReq:get_header_value("Host") of
>>>>>> -                undefined -> [];
>>>>>> -                Value1 -> Value1
>>>>>> -            end;
>>>>>> -        Value -> Value
>>>>>> -    end,
>>>>>> +    VHost = host(MochiReq),
>>>>>>   {VHostParts, VhostPort} = split_host_port(VHost),
>>>>>>   FinalMochiReq = case try_bind_vhost(VHosts,
>>>>> lists:reverse(VHostParts),
>>>>>>           VhostPort, VPathParts) of
>>>>>> @@ -133,14 +125,14 @@ append_path("/"=_Target, "/"=_Path) ->
>>>>>> append_path(Target, Path) ->
>>>>>>   Target ++ Path.
>>>>>> 
>>>>>> -% default redirect vhost handler
>>>>>> +% default redirect vhost handler
>>>>>> redirect_to_vhost(MochiReq, VhostTarget) ->
>>>>>>   Path = MochiReq:get(raw_path),
>>>>>>   Target = append_path(VhostTarget, Path),
>>>>>> 
>>>>>>   ?LOG_DEBUG("Vhost Target: '~p'~n", [Target]),
>>>>>> 
>>>>>> -    Headers = mochiweb_headers:enter("x-couchdb-vhost-path", Path,
>>>>>> +    Headers = mochiweb_headers:enter("x-couchdb-vhost-path", Path,
>>>>>>       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 = 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} = 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 = 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 = 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 = lists:foldl(fun
>>>>>>               ({_, ""}, Acc) ->
>>>>>> @@ -267,15 +272,15 @@ parse_vhost(Vhost) ->
>>>>>>           H1 = make_spec(H, []),
>>>>>>           {H1, P, string:tokens(Path, "/")}
>>>>>>   end.
>>>>>> -
>>>>>> +
>>>>>> 
>>>>>> split_host_port(HostAsString) ->
>>>>>>   case string:rchr(HostAsString, $:) of
>>>>>>       0 ->
>>>>>>           {split_host(HostAsString), '*'};
>>>>>>       N ->
>>>>>> -            HostPart = string:substr(HostAsString, 1, N-1),
>>>>>> -            case (catch
>>>>> erlang:list_to_integer(string:substr(HostAsString,
>>>>>> +            HostPart = 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 = couch_config:register(fun ?MODULE:config_change/2),
>>>>>> -
>>>>>> +
>>>>>>   %% load configuration
>>>>>>   {VHostGlobals, VHosts, Fun} = load_conf(),
>>>>>>   State = #vhosts_state{
>>>>>> 
>>>>>> 
>>>>> 
>> http://git-wip-us.apache.org/repos/asf/couchdb/blob/0777262f/test/etap/231_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 implied.
>> See
>>>>> the
>>>>>> +% License for the specific language governing permissions and
>>>>> limitations under
>>>>>> +% the License.
>>>>>> +
>>>>>> +-record(user_ctx, {
>>>>>> +    name = null,
>>>>>> +    roles = [],
>>>>>> +    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=[<<"_admin">>]}}.
>>>>>> +
>>>>>> +set_admin_password(UserName, Password) ->
>>>>>> +    Salt = binary_to_list(couch_uuids:random()),
>>>>>> +    Hashed = 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} = couch_db:create(list_to_binary(dbname()),
>>>>> [admin_user_ctx()]),
>>>>>> +    {ok, Db1} = couch_db:create(list_to_binary(dbname1()),
>>>>> [admin_user_ctx()]),
>>>>>> +    {ok, Db2} = 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 = couch_config:set("httpd", "enable_cors", "true", false),
>>>>>> +    ok = couch_config:set("cors", "origins", "http://example.com";,
>>>>> 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 = 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-Origin
>>>>>> +test_no_headers_server() ->
>>>>>> +    Headers = [{"Origin", "http://127.0.0.1"}],
>>>>>> +    {ok, _, Resp, _} = 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-Origin
>>>>>> +test_no_headers_db() ->
>>>>>> +    Headers = [{"Origin", "http://127.0.0.1"}],
>>>>>> +    Url = server() ++ "etap-test-db",
>>>>>> +    {ok, _, Resp, _} = 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 = [{"Origin", "http://127.0.0.1"}],
>>>>>> +    {ok, _, RespHeaders, _} = ibrowse:send_req(server(), Headers,
>> get,
>>>>> []),
>>>>>> +    etap:is(proplists:get_value("Access-Control-Allow-Origin",
>>>>> RespHeaders),
>>>>>> +            undefined,
>>>>>> +            "Specified invalid origin, no Access").
>>>>>> +
>>>>>> +test_incorrect_origin_preflight_request() ->
>>>>>> +    Headers = [{"Origin", "http://127.0.0.1"},
>>>>>> +               {"Access-Control-Request-Method", "GET"}],
>>>>>> +    {ok, _, RespHeaders, _} = ibrowse:send_req(server(), Headers,
>>>>> options, []),
>>>>>> +    etap:is(proplists:get_value("Access-Control-Allow-Origin",
>>>>> RespHeaders),
>>>>>> +            undefined,
>>>>>> +            "invalid origin").
>>>>>> +
>>>>>> +test_preflight_request() ->
>>>>>> +    Headers = [{"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 = [{"Origin", "http://example.com"}],
>>>>>> +    Url = 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 = server() ++ "etap-test-db",
>>>>>> +    Headers = [{"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 = [{"Origin", "http://example.com"}],
>>>>>> +    Url = 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 = [{"Origin", "http://example.com"}],
>>>>>> +    Url = 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 = server() ++ "etap-test-db2",
>>>>>> +    Headers = [{"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 = [{"Origin", "http://example.com"}],
>>>>>> +    Url = 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.
>>>>>> 
>>>>> 
>>>>> 
>>> 
>> 

Reply via email to