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 <[email protected]> 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 <[email protected]>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 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. >> >>>>>> >> >>>>> >> >>>>> >> >>> >> >> >> >> >
