summaryrefslogtreecommitdiff
path: root/src/chttpd/src/chttpd_cors.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/chttpd/src/chttpd_cors.erl')
-rw-r--r--src/chttpd/src/chttpd_cors.erl414
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]).