Introduce vhosts configuration into CORS In order to remove code duplication we move vhosts support from couch_http_cors into chttpd_cors. We also dispatch chttpd:send_response to couch_http which does call chttpd_cors:headers. In order to avoid double injection of CORS headers we check for existance of "Access-Control-Allow-Origin" in response headers.
COUCHDB-2945 Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch-httpd/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch-httpd/commit/7e5ea869 Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch-httpd/tree/7e5ea869 Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch-httpd/diff/7e5ea869 Branch: refs/heads/split_out_httpd_stack Commit: 7e5ea86950e7ccfc1cee17663e352a683c1da282 Parents: 265c82c Author: ILYA Khlopotov <iil...@ca.ibm.com> Authored: Tue Feb 9 13:22:05 2016 -0800 Committer: ILYA Khlopotov <iil...@ca.ibm.com> Committed: Tue Mar 1 08:35:09 2016 -0800 ---------------------------------------------------------------------- src/couch_httpd_cors.erl | 67 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-couch-httpd/blob/7e5ea869/src/couch_httpd_cors.erl ---------------------------------------------------------------------- diff --git a/src/couch_httpd_cors.erl b/src/couch_httpd_cors.erl index 85669df..a1ebbd5 100644 --- a/src/couch_httpd_cors.erl +++ b/src/couch_httpd_cors.erl @@ -159,17 +159,24 @@ handle_preflight_request(Req, Config, Origin) -> headers(Req, RequestHeaders) -> - case get_origin(Req) of - undefined -> - %% If the Origin header is not present terminate - %% this set of steps. The request is outside the scope - %% of this specification. - %% http://www.w3.org/TR/cors/#resource-processing-model - RequestHeaders; - Origin -> - headers(Req, RequestHeaders, Origin, get_cors_config(Req)) + case is_preflight_response(RequestHeaders) of + false -> + case get_origin(Req) of + undefined -> + %% If the Origin header is not present terminate + %% this set of steps. The request is outside the scope + %% of this specification. + %% http://www.w3.org/TR/cors/#resource-processing-model + RequestHeaders; + Origin -> + headers(Req, RequestHeaders, Origin, get_cors_config(Req)) + end; + true -> + RequestHeaders end. +is_preflight_response(Headers) -> + lists:keymember("Access-Control-Allow-Origin", 1, Headers). headers(_Req, RequestHeaders, undefined, _Config) -> RequestHeaders; @@ -272,28 +279,31 @@ allow_credentials(Config, Origin) -> get_origin_config(Config, Origin, <<"allow_credentials">>, ?CORS_DEFAULT_ALLOW_CREDENTIALS). -get_cors_config(#httpd{cors_config = undefined}) -> +get_cors_config(#httpd{cors_config = undefined, mochi_req = MochiReq}) -> + Host = couch_httpd_vhost:host(MochiReq), + EnableCors = config:get("httpd", "enable_cors", "false") =:= "true", AllowCredentials = config:get("cors", "credentials", "false") =:= "true", - AllowHeaders = case config:get("cors", "headers", undefined) of + + AllowHeaders = case cors_config(Host, "headers", undefined) of undefined -> ?SUPPORTED_HEADERS; AllowHeaders0 -> [to_lower(H) || H <- split_list(AllowHeaders0)] end, - AllowMethods = case config:get("cors", "methods", undefined) of + AllowMethods = case cors_config(Host, "methods", undefined) of undefined -> ?SUPPORTED_METHODS; AllowMethods0 -> split_list(AllowMethods0) end, - ExposedHeaders = case config:get("cors", "exposed_headers", undefined) of + ExposedHeaders = case cors_config(Host, "exposed_headers", undefined) of undefined -> ?COUCH_HEADERS; ExposedHeaders0 -> [to_lower(H) || H <- split_list(ExposedHeaders0)] end, - Origins0 = binary_split_list(config:get("cors", "origins", [])), + Origins0 = binary_split_list(cors_config(Host, "origins", [])), Origins = [{O, {[]}} || O <- Origins0], [ {<<"enable_cors">>, EnableCors}, @@ -306,6 +316,35 @@ get_cors_config(#httpd{cors_config = undefined}) -> get_cors_config(#httpd{cors_config = Config}) -> Config. +cors_config(Host, Key, Default) -> + config:get(cors_section(Host), Key, + config:get("cors", Key, Default)). + +cors_section(Host0) -> + {Host, _Port} = split_host_port(Host0), + "cors:" ++ Host. + +split_host_port(HostAsString) -> + % split at semicolon ":" + Split = string:rchr(HostAsString, $:), + split_host_port(HostAsString, Split). + +split_host_port(HostAsString, 0) -> + % no semicolon + {HostAsString, '*'}; +split_host_port(HostAsString, N) -> + HostPart = string:substr(HostAsString, 1, N-1), + % parse out port + % is there a nicer way? + case (catch erlang:list_to_integer(string:substr(HostAsString, + N+1, length(HostAsString)))) of + {'EXIT', _} -> + {HostAsString, '*'}; + Port -> + {HostPart, Port} + end. + + is_cors_enabled(Config) -> case get(disable_couch_httpd_cors) of undefined ->