diff options
Diffstat (limited to 'src/chttpd/src/chttpd_cors.erl')
-rw-r--r-- | src/chttpd/src/chttpd_cors.erl | 414 |
1 files changed, 0 insertions, 414 deletions
diff --git a/src/chttpd/src/chttpd_cors.erl b/src/chttpd/src/chttpd_cors.erl deleted file mode 100644 index 70d3163ec..000000000 --- a/src/chttpd/src/chttpd_cors.erl +++ /dev/null @@ -1,414 +0,0 @@ -% 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. - --module(chttpd_cors). - --export([ - maybe_handle_preflight_request/1, - maybe_handle_preflight_request/2, - headers/2, - headers/4 -]). --export([ - is_cors_enabled/1, - get_cors_config/1 -]). - --include_lib("couch/include/couch_db.hrl"). --include_lib("chttpd/include/chttpd_cors.hrl"). - -%% http://www.w3.org/TR/cors/#resource-preflight-requests - -maybe_handle_preflight_request(#httpd{method = Method}) when Method /= 'OPTIONS' -> - not_preflight; -maybe_handle_preflight_request(Req) -> - case maybe_handle_preflight_request(Req, get_cors_config(Req)) of - not_preflight -> - not_preflight; - {ok, PreflightHeaders} -> - chttpd:send_response_no_cors(Req, 204, PreflightHeaders, <<>>) - end. - -maybe_handle_preflight_request(#httpd{} = Req, Config) -> - case is_cors_enabled(Config) of - true -> - case preflight_request(Req, Config) of - {ok, PreflightHeaders} -> - {ok, PreflightHeaders}; - not_preflight -> - not_preflight; - UnknownError -> - couch_log:error( - "Unknown response of chttpd_cors:preflight_request(~p): ~p", - [Req, UnknownError] - ), - not_preflight - end; - false -> - not_preflight - end. - -preflight_request(Req, Config) -> - 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-preflight-requests - not_preflight; - Origin -> - AcceptedOrigins = get_accepted_origins(Req, Config), - AcceptAll = lists:member(<<"*">>, AcceptedOrigins), - - HandlerFun = fun() -> - handle_preflight_request(Req, Config, Origin) - end, - - %% We either need to accept all origins or have it listed - %% in our origins. Origin can only contain a single origin - %% as the user agent will not follow redirects [1]. If the - %% value of the Origin header is not a case-sensitive - %% match for any of the values in list of origins do not - %% set any additional headers and terminate this set - %% of steps [1]. - %% - %% [1]: http://www.w3.org/TR/cors/#resource-preflight-requests - %% - %% TODO: Square against multi origin Security Considerations and the - %% Vary header - %% - case AcceptAll orelse lists:member(Origin, AcceptedOrigins) of - true -> HandlerFun(); - false -> not_preflight - end - end. - -handle_preflight_request(Req, Config, Origin) -> - case chttpd:header_value(Req, "Access-Control-Request-Method") of - undefined -> - %% If there is no Access-Control-Request-Method header - %% or if parsing failed, do not set any additional headers - %% and terminate this set of steps. The request is outside - %% the scope of this specification. - %% http://www.w3.org/TR/cors/#resource-preflight-requests - not_preflight; - Method -> - SupportedMethods = get_origin_config( - Config, - Origin, - <<"allow_methods">>, - ?SUPPORTED_METHODS - ), - - SupportedHeaders = get_origin_config( - Config, - Origin, - <<"allow_headers">>, - ?SUPPORTED_HEADERS - ), - - %% get max age - MaxAge = couch_util:get_value( - <<"max_age">>, - Config, - ?CORS_DEFAULT_MAX_AGE - ), - - PreflightHeaders0 = maybe_add_credentials(Config, Origin, [ - {"Access-Control-Allow-Origin", binary_to_list(Origin)}, - {"Access-Control-Max-Age", MaxAge}, - {"Access-Control-Allow-Methods", string:join(SupportedMethods, ", ")} - ]), - - case lists:member(Method, SupportedMethods) of - true -> - %% method ok , check headers - AccessHeaders = chttpd:header_value( - Req, - "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 = [ - to_lower(H) - || H <- split_headers(Headers) - ], - {Headers, RH} - end, - %% check if headers are supported - case ReqHeaders -- SupportedHeaders of - [] -> - PreflightHeaders = - PreflightHeaders0 ++ - [{"Access-Control-Allow-Headers", FinalReqHeaders}], - {ok, PreflightHeaders}; - _ -> - not_preflight - end; - false -> - %% If method is not a case-sensitive match for any of - %% the values in list of methods do not set any additional - %% headers and terminate this set of steps. - %% http://www.w3.org/TR/cors/#resource-preflight-requests - not_preflight - end - end. - -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)) - end. - -headers(_Req, RequestHeaders, undefined, _Config) -> - RequestHeaders; -headers(Req, RequestHeaders, Origin, Config) when is_list(Origin) -> - headers(Req, RequestHeaders, ?l2b(string:to_lower(Origin)), Config); -headers(Req, RequestHeaders, Origin, Config) -> - case is_cors_enabled(Config) of - true -> - AcceptedOrigins = get_accepted_origins(Req, Config), - CorsHeaders = handle_headers(Config, Origin, AcceptedOrigins), - ExposedCouchHeaders = couch_util:get_value( - <<"exposed_headers">>, Config, ?COUCH_HEADERS - ), - maybe_apply_headers(CorsHeaders, RequestHeaders, ExposedCouchHeaders); - false -> - RequestHeaders - end. - -maybe_apply_headers([], RequestHeaders, _ExposedCouchHeaders) -> - RequestHeaders; -maybe_apply_headers(CorsHeaders, RequestHeaders, ExposedCouchHeaders) -> - %% Find all non ?SIMPLE_HEADERS and and non ?SIMPLE_CONTENT_TYPE_VALUES, - %% expose those through Access-Control-Expose-Headers, allowing - %% the client to access them in the browser. Also append in - %% ?COUCH_HEADERS, as further headers may be added later that - %% need to be exposed. - %% return: RequestHeaders ++ CorsHeaders ++ ACEH - - ExposedHeaders0 = simple_headers([K || {K, _V} <- RequestHeaders]), - - %% If Content-Type is not in ExposedHeaders, and the Content-Type - %% is not a member of ?SIMPLE_CONTENT_TYPE_VALUES, then add it - %% into the list of ExposedHeaders - ContentType = proplists:get_value("content-type", ExposedHeaders0), - IncludeContentType = - case ContentType of - undefined -> - false; - _ -> - lists:member(string:to_lower(ContentType), ?SIMPLE_CONTENT_TYPE_VALUES) - end, - ExposedHeaders = - case IncludeContentType of - false -> - ["content-type" | lists:delete("content-type", ExposedHeaders0)]; - true -> - ExposedHeaders0 - end, - - %% ExposedCouchHeaders may get added later, so expose them by default - ACEH = [ - {"Access-Control-Expose-Headers", string:join(ExposedHeaders ++ ExposedCouchHeaders, ", ")} - ], - CorsHeaders ++ RequestHeaders ++ ACEH. - -simple_headers(Headers) -> - LCHeaders = [to_lower(H) || H <- Headers], - lists:filter(fun(H) -> lists:member(H, ?SIMPLE_HEADERS) end, LCHeaders). - -to_lower(String) when is_binary(String) -> - to_lower(?b2l(String)); -to_lower(String) -> - string:to_lower(String). - -handle_headers(_Config, _Origin, []) -> - []; -handle_headers(Config, Origin, AcceptedOrigins) -> - AcceptAll = lists:member(<<"*">>, AcceptedOrigins), - case AcceptAll orelse lists:member(Origin, AcceptedOrigins) of - true -> - make_cors_header(Config, Origin); - false -> - %% If the value of the Origin header is not a - %% case-sensitive match for any of the values - %% in list of origins, do not set any additional - %% headers and terminate this set of steps. - %% http://www.w3.org/TR/cors/#resource-requests - [] - end. - -make_cors_header(Config, Origin) -> - Headers = [{"Access-Control-Allow-Origin", binary_to_list(Origin)}], - maybe_add_credentials(Config, Origin, Headers). - -%% util - -maybe_add_credentials(Config, Origin, Headers) -> - case allow_credentials(Config, Origin) of - false -> - Headers; - true -> - Headers ++ [{"Access-Control-Allow-Credentials", "true"}] - end. - -allow_credentials(_Config, <<"*">>) -> - false; -allow_credentials(Config, Origin) -> - get_origin_config( - Config, - Origin, - <<"allow_credentials">>, - ?CORS_DEFAULT_ALLOW_CREDENTIALS - ). - -get_cors_config(#httpd{cors_config = undefined, mochi_req = MochiReq}) -> - Host = couch_httpd_vhost:host(MochiReq), - - EnableCors = chttpd_util:get_chttpd_config_boolean("enable_cors", false), - AllowCredentials = cors_config(Host, "credentials", "false") =:= "true", - - AllowHeaders = - case cors_config(Host, "headers", undefined) of - undefined -> - ?SUPPORTED_HEADERS; - AllowHeaders0 -> - [to_lower(H) || H <- split_list(AllowHeaders0)] - end, - AllowMethods = - case cors_config(Host, "methods", undefined) of - undefined -> - ?SUPPORTED_METHODS; - AllowMethods0 -> - split_list(AllowMethods0) - end, - ExposedHeaders = - case cors_config(Host, "exposed_headers", undefined) of - undefined -> - ?COUCH_HEADERS; - ExposedHeaders0 -> - [to_lower(H) || H <- split_list(ExposedHeaders0)] - end, - MaxAge = cors_config(Host, "max_age", ?CORS_DEFAULT_MAX_AGE), - Origins0 = binary_split_list(cors_config(Host, "origins", [])), - Origins = [{O, {[]}} || O <- Origins0], - [ - {<<"enable_cors">>, EnableCors}, - {<<"allow_credentials">>, AllowCredentials}, - {<<"allow_methods">>, AllowMethods}, - {<<"allow_headers">>, AllowHeaders}, - {<<"exposed_headers">>, ExposedHeaders}, - {<<"max_age">>, MaxAge}, - {<<"origins">>, {Origins}} - ]; -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(HostValue) -> - HostPort = maybe_strip_scheme(HostValue), - Host = hd(string:tokens(HostPort, ":")), - "cors:" ++ Host. - -maybe_strip_scheme(Host) -> - case string:str(Host, "://") of - 0 -> Host; - N -> string:substr(Host, N + 3) - end. - -is_cors_enabled(Config) -> - case get(disable_couch_httpd_cors) of - undefined -> - put(disable_couch_httpd_cors, true); - _ -> - ok - end, - couch_util:get_value(<<"enable_cors">>, Config, false). - -%% Get a list of {Origin, OriginConfig} tuples -%% ie: get_origin_configs(Config) -> -%% [ -%% {<<"http://foo.com">>, -%% { -%% [ -%% {<<"allow_credentials">>, true}, -%% {<<"allow_methods">>, [<<"POST">>]} -%% ] -%% } -%% }, -%% {<<"http://baz.com">>, {[]}} -%% ] -get_origin_configs(Config) -> - {Origins} = couch_util:get_value(<<"origins">>, Config, {[]}), - Origins. - -%% Get config for an individual Origin -%% ie: get_origin_config(Config, <<"http://foo.com">>) -> -%% [ -%% {<<"allow_credentials">>, true}, -%% {<<"allow_methods">>, [<<"POST">>]} -%% ] -get_origin_config(Config, Origin) -> - OriginConfigs = get_origin_configs(Config), - {OriginConfig} = couch_util:get_value(Origin, OriginConfigs, {[]}), - OriginConfig. - -%% Get config of a single key for an individual Origin -%% ie: get_origin_config(Config, <<"http://foo.com">>, <<"allow_methods">>, []) -%% [<<"POST">>] -get_origin_config(Config, Origin, Key, Default) -> - OriginConfig = get_origin_config(Config, Origin), - couch_util:get_value( - Key, - OriginConfig, - couch_util:get_value(Key, Config, Default) - ). - -get_origin(Req) -> - case chttpd:header_value(Req, "Origin") of - undefined -> - undefined; - Origin -> - ?l2b(Origin) - end. - -get_accepted_origins(_Req, Config) -> - lists:map(fun({K, _V}) -> K end, get_origin_configs(Config)). - -split_list(S) -> - re:split(S, "\\s*,\\s*", [trim, {return, list}]). - -binary_split_list(S) -> - [list_to_binary(E) || E <- split_list(S)]. - -split_headers(H) -> - re:split(H, ",\\s*", [{return, list}, trim]). |