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. > > > >
