You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by be...@apache.org on 2014/02/13 19:12:50 UTC
[52/57] [abbrv] remove couch_httpd
http://git-wip-us.apache.org/repos/asf/couchdb/blob/c0855434/apps/couch_httpd/src/couch_httpd_oauth.erl
----------------------------------------------------------------------
diff --git a/apps/couch_httpd/src/couch_httpd_oauth.erl b/apps/couch_httpd/src/couch_httpd_oauth.erl
deleted file mode 100644
index 07229d3..0000000
--- a/apps/couch_httpd/src/couch_httpd_oauth.erl
+++ /dev/null
@@ -1,387 +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(couch_httpd_oauth).
-
--include_lib("couch/include/couch_db.hrl").
--include_lib("couch/include/couch_js_functions.hrl").
-
--export([oauth_authentication_handler/1, handle_oauth_req/1]).
-
--define(OAUTH_DDOC_ID, <<"_design/oauth">>).
--define(OAUTH_VIEW_NAME, <<"oauth_credentials">>).
-
--record(callback_params, {
- consumer,
- token,
- token_secret,
- url,
- signature,
- params,
- username
-}).
-
-% OAuth auth handler using per-node user db
-oauth_authentication_handler(Req) ->
- serve_oauth(Req, fun oauth_auth_callback/2, true).
-
-
-oauth_auth_callback(Req, #callback_params{token_secret = undefined}) ->
- couch_httpd:send_error(
- Req, 400, <<"invalid_token">>, <<"Invalid OAuth token.">>);
-
-oauth_auth_callback(#httpd{mochi_req = MochiReq} = Req, CbParams) ->
- Method = atom_to_list(MochiReq:get(method)),
- #callback_params{
- consumer = Consumer,
- token_secret = TokenSecret,
- url = Url,
- signature = Sig,
- params = Params,
- username = User
- } = CbParams,
- case oauth:verify(Sig, Method, Url, Params, Consumer, TokenSecret) of
- true ->
- set_user_ctx(Req, User);
- false ->
- ?LOG_DEBUG("OAuth handler: signature verification failed for user `~p`~n"
- "Received signature is `~p`~n"
- "HTTP method is `~p`~n"
- "URL is `~p`~n"
- "Parameters are `~p`~n"
- "Consumer is `~p`, token secret is `~p`~n"
- "Expected signature was `~p`~n",
- [User, Sig, Method, Url, Params, Consumer, TokenSecret,
- oauth:signature(Method, Url, Params, Consumer, TokenSecret)]),
- Req
- end.
-
-
-% Look up the consumer key and get the roles to give the consumer
-set_user_ctx(_Req, undefined) ->
- throw({bad_request, unknown_oauth_token});
-set_user_ctx(Req, Name) ->
- case couch_auth_cache:get_user_creds(Name) of
- nil ->
- ?LOG_DEBUG("OAuth handler: user `~p` credentials not found", [Name]),
- Req;
- User ->
- Roles = couch_util:get_value(<<"roles">>, User, []),
- Req#httpd{user_ctx=#user_ctx{name=Name, roles=Roles}}
- end.
-
-% OAuth request_token
-handle_oauth_req(#httpd{path_parts=[_OAuth, <<"request_token">>], method=Method}=Req1) ->
- serve_oauth(Req1, fun(Req, CbParams) ->
- #callback_params{
- consumer = Consumer,
- token_secret = TokenSecret,
- url = Url,
- signature = Sig,
- params = Params
- } = CbParams,
- case oauth:verify(
- Sig, atom_to_list(Method), Url, Params, Consumer, TokenSecret) of
- true ->
- ok(Req, <<"oauth_token=requestkey&oauth_token_secret=requestsecret">>);
- false ->
- invalid_signature(Req)
- end
- end, false);
-handle_oauth_req(#httpd{path_parts=[_OAuth, <<"authorize">>]}=Req) ->
- {ok, serve_oauth_authorize(Req)};
-handle_oauth_req(#httpd{path_parts=[_OAuth, <<"access_token">>], method='GET'}=Req1) ->
- serve_oauth(Req1, fun(Req, CbParams) ->
- #callback_params{
- consumer = Consumer,
- token = Token,
- url = Url,
- signature = Sig,
- params = Params
- } = CbParams,
- case Token of
- "requestkey" ->
- case oauth:verify(
- Sig, "GET", Url, Params, Consumer, "requestsecret") of
- true ->
- ok(Req,
- <<"oauth_token=accesskey&oauth_token_secret=accesssecret">>);
- false ->
- invalid_signature(Req)
- end;
- _ ->
- couch_httpd:send_error(
- Req, 400, <<"invalid_token">>, <<"Invalid OAuth token.">>)
- end
- end, false);
-handle_oauth_req(#httpd{path_parts=[_OAuth, <<"access_token">>]}=Req) ->
- couch_httpd:send_method_not_allowed(Req, "GET").
-
-invalid_signature(Req) ->
- couch_httpd:send_error(Req, 400, <<"invalid_signature">>, <<"Invalid signature value.">>).
-
-% This needs to be protected i.e. force user to login using HTTP Basic Auth or form-based login.
-serve_oauth_authorize(#httpd{method=Method}=Req1) ->
- case Method of
- 'GET' ->
- % Confirm with the User that they want to authenticate the Consumer
- serve_oauth(Req1, fun(Req, CbParams) ->
- #callback_params{
- consumer = Consumer,
- token_secret = TokenSecret,
- url = Url,
- signature = Sig,
- params = Params
- } = CbParams,
- case oauth:verify(
- Sig, "GET", Url, Params, Consumer, TokenSecret) of
- true ->
- ok(Req, <<"oauth_token=requestkey&",
- "oauth_token_secret=requestsecret">>);
- false ->
- invalid_signature(Req)
- end
- end, false);
- 'POST' ->
- % If the User has confirmed, we direct the User back to the Consumer with a verification code
- serve_oauth(Req1, fun(Req, CbParams) ->
- #callback_params{
- consumer = Consumer,
- token_secret = TokenSecret,
- url = Url,
- signature = Sig,
- params = Params
- } = CbParams,
- case oauth:verify(
- Sig, "POST", Url, Params, Consumer, TokenSecret) of
- true ->
- %redirect(oauth_callback, oauth_token, oauth_verifier),
- ok(Req, <<"oauth_token=requestkey&",
- "oauth_token_secret=requestsecret">>);
- false ->
- invalid_signature(Req)
- end
- end, false);
- _ ->
- couch_httpd:send_method_not_allowed(Req1, "GET,POST")
- end.
-
-serve_oauth(#httpd{mochi_req=MochiReq}=Req, Fun, FailSilently) ->
- % 1. In the HTTP Authorization header as defined in OAuth HTTP Authorization Scheme.
- % 2. As the HTTP POST request body with a content-type of application/x-www-form-urlencoded.
- % 3. Added to the URLs in the query part (as defined by [RFC3986] section 3).
- AuthHeader = case MochiReq:get_header_value("authorization") of
- undefined ->
- "";
- Else ->
- [Head | Tail] = re:split(Else, "\\s", [{parts, 2}, {return, list}]),
- case [string:to_lower(Head) | Tail] of
- ["oauth", Rest] -> Rest;
- _ -> ""
- end
- end,
- HeaderParams = oauth:header_params_decode(AuthHeader),
- %Realm = couch_util:get_value("realm", HeaderParams),
-
- % get requested path
- RequestedPath = case MochiReq:get_header_value("x-couchdb-requested-path") of
- undefined ->
- case MochiReq:get_header_value("x-couchdb-vhost-path") of
- undefined ->
- MochiReq:get(raw_path);
- VHostPath ->
- VHostPath
- end;
- RequestedPath0 ->
- RequestedPath0
- end,
- {_, QueryString, _} = mochiweb_util:urlsplit_path(RequestedPath),
-
- Params = proplists:delete("realm", HeaderParams) ++ mochiweb_util:parse_qs(QueryString),
-
- ?LOG_DEBUG("OAuth Params: ~p", [Params]),
- case couch_util:get_value("oauth_version", Params, "1.0") of
- "1.0" ->
- case couch_util:get_value("oauth_consumer_key", Params, undefined) of
- undefined ->
- case FailSilently of
- true -> Req;
- false -> couch_httpd:send_error(Req, 400, <<"invalid_consumer">>, <<"Invalid consumer.">>)
- end;
- ConsumerKey ->
- Url = couch_httpd:absolute_uri(Req, RequestedPath),
- case get_callback_params(ConsumerKey, Params, Url) of
- {ok, CallbackParams} ->
- Fun(Req, CallbackParams);
- invalid_consumer_token_pair ->
- couch_httpd:send_error(
- Req, 400,
- <<"invalid_consumer_token_pair">>,
- <<"Invalid consumer and token pair.">>);
- {error, {Error, Reason}} ->
- couch_httpd:send_error(Req, 400, Error, Reason)
- end
- end;
- _ ->
- couch_httpd:send_error(Req, 400, <<"invalid_oauth_version">>, <<"Invalid OAuth version.">>)
- end.
-
-
-get_callback_params(ConsumerKey, Params, Url) ->
- Token = couch_util:get_value("oauth_token", Params),
- SigMethod = sig_method(Params),
- CbParams0 = #callback_params{
- token = Token,
- signature = couch_util:get_value("oauth_signature", Params),
- params = proplists:delete("oauth_signature", Params),
- url = Url
- },
- case oauth_credentials_info(Token, ConsumerKey) of
- nil ->
- invalid_consumer_token_pair;
- {error, _} = Err ->
- Err;
- {OauthCreds} ->
- User = couch_util:get_value(<<"username">>, OauthCreds, []),
- ConsumerSecret = ?b2l(couch_util:get_value(
- <<"consumer_secret">>, OauthCreds, <<>>)),
- TokenSecret = ?b2l(couch_util:get_value(
- <<"token_secret">>, OauthCreds, <<>>)),
- case (User =:= []) orelse (ConsumerSecret =:= []) orelse
- (TokenSecret =:= []) of
- true ->
- invalid_consumer_token_pair;
- false ->
- CbParams = CbParams0#callback_params{
- consumer = {ConsumerKey, ConsumerSecret, SigMethod},
- token_secret = TokenSecret,
- username = User
- },
- ?LOG_DEBUG("Got OAuth credentials, for ConsumerKey `~p` and "
- "Token `~p`, from the views, User: `~p`, "
- "ConsumerSecret: `~p`, TokenSecret: `~p`",
- [ConsumerKey, Token, User, ConsumerSecret, TokenSecret]),
- {ok, CbParams}
- end
- end.
-
-
-sig_method(Params) ->
- sig_method_1(couch_util:get_value("oauth_signature_method", Params)).
-sig_method_1("PLAINTEXT") ->
- plaintext;
-% sig_method_1("RSA-SHA1") ->
-% rsa_sha1;
-sig_method_1("HMAC-SHA1") ->
- hmac_sha1;
-sig_method_1(_) ->
- undefined.
-
-
-ok(#httpd{mochi_req=MochiReq}, Body) ->
- {ok, MochiReq:respond({200, [], Body})}.
-
-
-oauth_credentials_info(Token, ConsumerKey) ->
- case use_auth_db() of
- {ok, Db} ->
- Result = case query_oauth_view(Db, [?l2b(ConsumerKey), ?l2b(Token)]) of
- [] ->
- nil;
- [Creds] ->
- Creds;
- [_ | _] ->
- Reason = iolist_to_binary(
- io_lib:format("Found multiple OAuth credentials for the pair "
- " (consumer_key: `~p`, token: `~p`)", [ConsumerKey, Token])),
- {error, {<<"oauth_token_consumer_key_pair">>, Reason}}
- end,
- couch_db:close(Db),
- Result;
- nil ->
- {
- case couch_config:get("oauth_consumer_secrets", ConsumerKey) of
- undefined -> [];
- ConsumerSecret -> [{<<"consumer_secret">>, ?l2b(ConsumerSecret)}]
- end
- ++
- case couch_config:get("oauth_token_secrets", Token) of
- undefined -> [];
- TokenSecret -> [{<<"token_secret">>, ?l2b(TokenSecret)}]
- end
- ++
- case couch_config:get("oauth_token_users", Token) of
- undefined -> [];
- User -> [{<<"username">>, ?l2b(User)}]
- end
- }
- end.
-
-
-use_auth_db() ->
- case couch_config:get("couch_httpd_oauth", "use_users_db", "false") of
- "false" ->
- nil;
- "true" ->
- AuthDb = open_auth_db(),
- {ok, _AuthDb2} = ensure_oauth_views_exist(AuthDb)
- end.
-
-
-open_auth_db() ->
- DbName = ?l2b(couch_config:get("couch_httpd_auth", "authentication_db")),
- DbOptions = [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}],
- {ok, AuthDb} = couch_db:open_int(DbName, DbOptions),
- AuthDb.
-
-
-ensure_oauth_views_exist(AuthDb) ->
- case couch_db:open_doc(AuthDb, ?OAUTH_DDOC_ID, []) of
- {ok, _DDoc} ->
- {ok, AuthDb};
- _ ->
- {ok, DDoc} = get_oauth_ddoc(),
- {ok, _Rev} = couch_db:update_doc(AuthDb, DDoc, []),
- {ok, _AuthDb2} = couch_db:reopen(AuthDb)
- end.
-
-
-get_oauth_ddoc() ->
- Json = {[
- {<<"_id">>, ?OAUTH_DDOC_ID},
- {<<"language">>, <<"javascript">>},
- {<<"views">>,
- {[
- {?OAUTH_VIEW_NAME,
- {[
- {<<"map">>, ?OAUTH_MAP_FUN}
- ]}
- }
- ]}
- }
- ]},
- {ok, couch_doc:from_json_obj(Json)}.
-
-
-query_oauth_view(Db, Key) ->
- ViewOptions = [
- {start_key, Key},
- {end_key, Key}
- ],
- Callback = fun({row, Row}, Acc) ->
- {ok, [couch_util:get_value(value, Row) | Acc]};
- (_, Acc) ->
- {ok, Acc}
- end,
- {ok, Result} = couch_mrview:query_view(
- Db, ?OAUTH_DDOC_ID, ?OAUTH_VIEW_NAME, ViewOptions, Callback, []),
- Result.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/c0855434/apps/couch_httpd/src/couch_httpd_proxy.erl
----------------------------------------------------------------------
diff --git a/apps/couch_httpd/src/couch_httpd_proxy.erl b/apps/couch_httpd/src/couch_httpd_proxy.erl
deleted file mode 100644
index bce2c7f..0000000
--- a/apps/couch_httpd/src/couch_httpd_proxy.erl
+++ /dev/null
@@ -1,426 +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(couch_httpd_proxy).
-
--export([handle_proxy_req/2]).
-
--include_lib("couch/include/couch_db.hrl").
--include_lib("ibrowse/include/ibrowse.hrl").
-
--define(TIMEOUT, infinity).
--define(PKT_SIZE, 4096).
-
-
-handle_proxy_req(Req, ProxyDest) ->
- Method = get_method(Req),
- Url = get_url(Req, ProxyDest),
- Version = get_version(Req),
- Headers = get_headers(Req),
- Body = get_body(Req),
- Options = [
- {http_vsn, Version},
- {headers_as_is, true},
- {response_format, binary},
- {stream_to, {self(), once}}
- ],
- case ibrowse:send_req(Url, Headers, Method, Body, Options, ?TIMEOUT) of
- {ibrowse_req_id, ReqId} ->
- stream_response(Req, ProxyDest, ReqId);
- {error, Reason} ->
- throw({error, Reason})
- end.
-
-
-get_method(#httpd{mochi_req=MochiReq}) ->
- case MochiReq:get(method) of
- Method when is_atom(Method) ->
- list_to_atom(string:to_lower(atom_to_list(Method)));
- Method when is_list(Method) ->
- list_to_atom(string:to_lower(Method));
- Method when is_binary(Method) ->
- list_to_atom(string:to_lower(?b2l(Method)))
- end.
-
-
-get_url(Req, ProxyDest) when is_binary(ProxyDest) ->
- get_url(Req, ?b2l(ProxyDest));
-get_url(#httpd{mochi_req=MochiReq}=Req, ProxyDest) ->
- BaseUrl = case mochiweb_util:partition(ProxyDest, "/") of
- {[], "/", _} -> couch_httpd:absolute_uri(Req, ProxyDest);
- _ -> ProxyDest
- end,
- ProxyPrefix = "/" ++ ?b2l(hd(Req#httpd.path_parts)),
- RequestedPath = MochiReq:get(raw_path),
- case mochiweb_util:partition(RequestedPath, ProxyPrefix) of
- {[], ProxyPrefix, []} ->
- BaseUrl;
- {[], ProxyPrefix, [$/ | DestPath]} ->
- remove_trailing_slash(BaseUrl) ++ "/" ++ DestPath;
- {[], ProxyPrefix, DestPath} ->
- remove_trailing_slash(BaseUrl) ++ "/" ++ DestPath;
- _Else ->
- throw({invalid_url_path, {ProxyPrefix, RequestedPath}})
- end.
-
-get_version(#httpd{mochi_req=MochiReq}) ->
- MochiReq:get(version).
-
-
-get_headers(#httpd{mochi_req=MochiReq}) ->
- to_ibrowse_headers(mochiweb_headers:to_list(MochiReq:get(headers)), []).
-
-to_ibrowse_headers([], Acc) ->
- lists:reverse(Acc);
-to_ibrowse_headers([{K, V} | Rest], Acc) when is_atom(K) ->
- to_ibrowse_headers([{atom_to_list(K), V} | Rest], Acc);
-to_ibrowse_headers([{K, V} | Rest], Acc) when is_list(K) ->
- case string:to_lower(K) of
- "content-length" ->
- to_ibrowse_headers(Rest, [{content_length, V} | Acc]);
- % This appears to make ibrowse too smart.
- %"transfer-encoding" ->
- % to_ibrowse_headers(Rest, [{transfer_encoding, V} | Acc]);
- _ ->
- to_ibrowse_headers(Rest, [{K, V} | Acc])
- end.
-
-get_body(#httpd{method='GET'}) ->
- fun() -> eof end;
-get_body(#httpd{method='HEAD'}) ->
- fun() -> eof end;
-get_body(#httpd{method='DELETE'}) ->
- fun() -> eof end;
-get_body(#httpd{mochi_req=MochiReq}) ->
- case MochiReq:get(body_length) of
- undefined ->
- <<>>;
- {unknown_transfer_encoding, Unknown} ->
- exit({unknown_transfer_encoding, Unknown});
- chunked ->
- {fun stream_chunked_body/1, {init, MochiReq, 0}};
- 0 ->
- <<>>;
- Length when is_integer(Length) andalso Length > 0 ->
- {fun stream_length_body/1, {init, MochiReq, Length}};
- Length ->
- exit({invalid_body_length, Length})
- end.
-
-
-remove_trailing_slash(Url) ->
- rem_slash(lists:reverse(Url)).
-
-rem_slash([]) ->
- [];
-rem_slash([$\s | RevUrl]) ->
- rem_slash(RevUrl);
-rem_slash([$\t | RevUrl]) ->
- rem_slash(RevUrl);
-rem_slash([$\r | RevUrl]) ->
- rem_slash(RevUrl);
-rem_slash([$\n | RevUrl]) ->
- rem_slash(RevUrl);
-rem_slash([$/ | RevUrl]) ->
- rem_slash(RevUrl);
-rem_slash(RevUrl) ->
- lists:reverse(RevUrl).
-
-
-stream_chunked_body({init, MReq, 0}) ->
- % First chunk, do expect-continue dance.
- init_body_stream(MReq),
- stream_chunked_body({stream, MReq, 0, [], ?PKT_SIZE});
-stream_chunked_body({stream, MReq, 0, Buf, BRem}) ->
- % Finished a chunk, get next length. If next length
- % is 0, its time to try and read trailers.
- {CRem, Data} = read_chunk_length(MReq),
- case CRem of
- 0 ->
- BodyData = lists:reverse(Buf, Data),
- {ok, BodyData, {trailers, MReq, [], ?PKT_SIZE}};
- _ ->
- stream_chunked_body(
- {stream, MReq, CRem, [Data | Buf], BRem-size(Data)}
- )
- end;
-stream_chunked_body({stream, MReq, CRem, Buf, BRem}) when BRem =< 0 ->
- % Time to empty our buffers to the upstream socket.
- BodyData = lists:reverse(Buf),
- {ok, BodyData, {stream, MReq, CRem, [], ?PKT_SIZE}};
-stream_chunked_body({stream, MReq, CRem, Buf, BRem}) ->
- % Buffer some more data from the client.
- Length = lists:min([CRem, BRem]),
- Socket = MReq:get(socket),
- NewState = case mochiweb_socket:recv(Socket, Length, ?TIMEOUT) of
- {ok, Data} when size(Data) == CRem ->
- case mochiweb_socket:recv(Socket, 2, ?TIMEOUT) of
- {ok, <<"\r\n">>} ->
- {stream, MReq, 0, [<<"\r\n">>, Data | Buf], BRem-Length-2};
- _ ->
- exit(normal)
- end;
- {ok, Data} ->
- {stream, MReq, CRem-Length, [Data | Buf], BRem-Length};
- _ ->
- exit(normal)
- end,
- stream_chunked_body(NewState);
-stream_chunked_body({trailers, MReq, Buf, BRem}) when BRem =< 0 ->
- % Empty our buffers and send data upstream.
- BodyData = lists:reverse(Buf),
- {ok, BodyData, {trailers, MReq, [], ?PKT_SIZE}};
-stream_chunked_body({trailers, MReq, Buf, BRem}) ->
- % Read another trailer into the buffer or stop on an
- % empty line.
- Socket = MReq:get(socket),
- mochiweb_socket:setopts(Socket, [{packet, line}]),
- case mochiweb_socket:recv(Socket, 0, ?TIMEOUT) of
- {ok, <<"\r\n">>} ->
- mochiweb_socket:setopts(Socket, [{packet, raw}]),
- BodyData = lists:reverse(Buf, <<"\r\n">>),
- {ok, BodyData, eof};
- {ok, Footer} ->
- mochiweb_socket:setopts(Socket, [{packet, raw}]),
- NewState = {trailers, MReq, [Footer | Buf], BRem-size(Footer)},
- stream_chunked_body(NewState);
- _ ->
- exit(normal)
- end;
-stream_chunked_body(eof) ->
- % Tell ibrowse we're done sending data.
- eof.
-
-
-stream_length_body({init, MochiReq, Length}) ->
- % Do the expect-continue dance
- init_body_stream(MochiReq),
- stream_length_body({stream, MochiReq, Length});
-stream_length_body({stream, _MochiReq, 0}) ->
- % Finished streaming.
- eof;
-stream_length_body({stream, MochiReq, Length}) ->
- BufLen = lists:min([Length, ?PKT_SIZE]),
- case MochiReq:recv(BufLen) of
- <<>> -> eof;
- Bin -> {ok, Bin, {stream, MochiReq, Length-BufLen}}
- end.
-
-
-init_body_stream(MochiReq) ->
- Expect = case MochiReq:get_header_value("expect") of
- undefined ->
- undefined;
- Value when is_list(Value) ->
- string:to_lower(Value)
- end,
- case Expect of
- "100-continue" ->
- MochiReq:start_raw_response({100, gb_trees:empty()});
- _Else ->
- ok
- end.
-
-
-read_chunk_length(MochiReq) ->
- Socket = MochiReq:get(socket),
- mochiweb_socket:setopts(Socket, [{packet, line}]),
- case mochiweb_socket:recv(Socket, 0, ?TIMEOUT) of
- {ok, Header} ->
- mochiweb_socket:setopts(Socket, [{packet, raw}]),
- Splitter = fun(C) ->
- C =/= $\r andalso C =/= $\n andalso C =/= $\s
- end,
- {Hex, _Rest} = lists:splitwith(Splitter, ?b2l(Header)),
- {mochihex:to_int(Hex), Header};
- _ ->
- exit(normal)
- end.
-
-
-stream_response(Req, ProxyDest, ReqId) ->
- receive
- {ibrowse_async_headers, ReqId, "100", _} ->
- % ibrowse doesn't handle 100 Continue responses which
- % means we have to discard them so the proxy client
- % doesn't get confused.
- ibrowse:stream_next(ReqId),
- stream_response(Req, ProxyDest, ReqId);
- {ibrowse_async_headers, ReqId, Status, Headers} ->
- {Source, Dest} = get_urls(Req, ProxyDest),
- FixedHeaders = fix_headers(Source, Dest, Headers, []),
- case body_length(FixedHeaders) of
- chunked ->
- {ok, Resp} = couch_httpd:start_chunked_response(
- Req, list_to_integer(Status), FixedHeaders
- ),
- ibrowse:stream_next(ReqId),
- stream_chunked_response(Req, ReqId, Resp),
- {ok, Resp};
- Length when is_integer(Length) ->
- {ok, Resp} = couch_httpd:start_response_length(
- Req, list_to_integer(Status), FixedHeaders, Length
- ),
- ibrowse:stream_next(ReqId),
- stream_length_response(Req, ReqId, Resp),
- {ok, Resp};
- _ ->
- {ok, Resp} = couch_httpd:start_response(
- Req, list_to_integer(Status), FixedHeaders
- ),
- ibrowse:stream_next(ReqId),
- stream_length_response(Req, ReqId, Resp),
- % XXX: MochiWeb apparently doesn't look at the
- % response to see if it must force close the
- % connection. So we help it out here.
- erlang:put(mochiweb_request_force_close, true),
- {ok, Resp}
- end
- end.
-
-
-stream_chunked_response(Req, ReqId, Resp) ->
- receive
- {ibrowse_async_response, ReqId, {error, Reason}} ->
- throw({error, Reason});
- {ibrowse_async_response, ReqId, Chunk} ->
- couch_httpd:send_chunk(Resp, Chunk),
- ibrowse:stream_next(ReqId),
- stream_chunked_response(Req, ReqId, Resp);
- {ibrowse_async_response_end, ReqId} ->
- couch_httpd:last_chunk(Resp)
- end.
-
-
-stream_length_response(Req, ReqId, Resp) ->
- receive
- {ibrowse_async_response, ReqId, {error, Reason}} ->
- throw({error, Reason});
- {ibrowse_async_response, ReqId, Chunk} ->
- couch_httpd:send(Resp, Chunk),
- ibrowse:stream_next(ReqId),
- stream_length_response(Req, ReqId, Resp);
- {ibrowse_async_response_end, ReqId} ->
- ok
- end.
-
-
-get_urls(Req, ProxyDest) ->
- SourceUrl = couch_httpd:absolute_uri(Req, "/" ++ hd(Req#httpd.path_parts)),
- Source = parse_url(?b2l(iolist_to_binary(SourceUrl))),
- case (catch parse_url(ProxyDest)) of
- Dest when is_record(Dest, url) ->
- {Source, Dest};
- _ ->
- DestUrl = couch_httpd:absolute_uri(Req, ProxyDest),
- {Source, parse_url(DestUrl)}
- end.
-
-
-fix_headers(_, _, [], Acc) ->
- lists:reverse(Acc);
-fix_headers(Source, Dest, [{K, V} | Rest], Acc) ->
- Fixed = case string:to_lower(K) of
- "location" -> rewrite_location(Source, Dest, V);
- "content-location" -> rewrite_location(Source, Dest, V);
- "uri" -> rewrite_location(Source, Dest, V);
- "destination" -> rewrite_location(Source, Dest, V);
- "set-cookie" -> rewrite_cookie(Source, Dest, V);
- _ -> V
- end,
- fix_headers(Source, Dest, Rest, [{K, Fixed} | Acc]).
-
-
-rewrite_location(Source, #url{host=Host, port=Port, protocol=Proto}, Url) ->
- case (catch parse_url(Url)) of
- #url{host=Host, port=Port, protocol=Proto} = Location ->
- DestLoc = #url{
- protocol=Source#url.protocol,
- host=Source#url.host,
- port=Source#url.port,
- path=join_url_path(Source#url.path, Location#url.path)
- },
- url_to_url(DestLoc);
- #url{} ->
- Url;
- _ ->
- url_to_url(Source#url{path=join_url_path(Source#url.path, Url)})
- end.
-
-
-rewrite_cookie(_Source, _Dest, Cookie) ->
- Cookie.
-
-
-parse_url(Url) when is_binary(Url) ->
- ibrowse_lib:parse_url(?b2l(Url));
-parse_url(Url) when is_list(Url) ->
- ibrowse_lib:parse_url(?b2l(iolist_to_binary(Url))).
-
-
-join_url_path(Src, Dst) ->
- Src2 = case lists:reverse(Src) of
- "/" ++ RestSrc -> lists:reverse(RestSrc);
- _ -> Src
- end,
- Dst2 = case Dst of
- "/" ++ RestDst -> RestDst;
- _ -> Dst
- end,
- Src2 ++ "/" ++ Dst2.
-
-
-url_to_url(#url{host=Host, port=Port, path=Path, protocol=Proto} = Url) ->
- LPort = case {Proto, Port} of
- {http, 80} -> "";
- {https, 443} -> "";
- _ -> ":" ++ integer_to_list(Port)
- end,
- LPath = case Path of
- "/" ++ _RestPath -> Path;
- _ -> "/" ++ Path
- end,
- HostPart = case Url#url.host_type of
- ipv6_address ->
- "[" ++ Host ++ "]";
- _ ->
- Host
- end,
- atom_to_list(Proto) ++ "://" ++ HostPart ++ LPort ++ LPath.
-
-
-body_length(Headers) ->
- case is_chunked(Headers) of
- true -> chunked;
- _ -> content_length(Headers)
- end.
-
-
-is_chunked([]) ->
- false;
-is_chunked([{K, V} | Rest]) ->
- case string:to_lower(K) of
- "transfer-encoding" ->
- string:to_lower(V) == "chunked";
- _ ->
- is_chunked(Rest)
- end.
-
-content_length([]) ->
- undefined;
-content_length([{K, V} | Rest]) ->
- case string:to_lower(K) of
- "content-length" ->
- list_to_integer(V);
- _ ->
- content_length(Rest)
- end.
-
http://git-wip-us.apache.org/repos/asf/couchdb/blob/c0855434/apps/couch_httpd/src/couch_httpd_rewrite.erl
----------------------------------------------------------------------
diff --git a/apps/couch_httpd/src/couch_httpd_rewrite.erl b/apps/couch_httpd/src/couch_httpd_rewrite.erl
deleted file mode 100644
index 011c3c8..0000000
--- a/apps/couch_httpd/src/couch_httpd_rewrite.erl
+++ /dev/null
@@ -1,480 +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.
-%
-% bind_path is based on bind method from Webmachine
-
-
-%% @doc Module for URL rewriting by pattern matching.
-
--module(couch_httpd_rewrite).
--export([handle_rewrite_req/3]).
--include_lib("couch/include/couch_db.hrl").
-
--define(SEPARATOR, $\/).
--define(MATCH_ALL, {bind, <<"*">>}).
-
-
-%% doc The http rewrite handler. All rewriting is done from
-%% /dbname/_design/ddocname/_rewrite by default.
-%%
-%% each rules should be in rewrites member of the design doc.
-%% Ex of a complete rule :
-%%
-%% {
-%% ....
-%% "rewrites": [
-%% {
-%% "from": "",
-%% "to": "index.html",
-%% "method": "GET",
-%% "query": {}
-%% }
-%% ]
-%% }
-%%
-%% from: is the path rule used to bind current uri to the rule. It
-%% use pattern matching for that.
-%%
-%% to: rule to rewrite an url. It can contain variables depending on binding
-%% variables discovered during pattern matching and query args (url args and from
-%% the query member.)
-%%
-%% method: method to bind the request method to the rule. by default "*"
-%% query: query args you want to define they can contain dynamic variable
-%% by binding the key to the bindings
-%%
-%%
-%% to and from are path with patterns. pattern can be string starting with ":" or
-%% "*". ex:
-%% /somepath/:var/*
-%%
-%% This path is converted in erlang list by splitting "/". Each var are
-%% converted in atom. "*" is converted to '*' atom. The pattern matching is done
-%% by splitting "/" in request url in a list of token. A string pattern will
-%% match equal token. The star atom ('*' in single quotes) will match any number
-%% of tokens, but may only be present as the last pathtern in a pathspec. If all
-%% tokens are matched and all pathterms are used, then the pathspec matches. It works
-%% like webmachine. Each identified token will be reused in to rule and in query
-%%
-%% The pattern matching is done by first matching the request method to a rule. by
-%% default all methods match a rule. (method is equal to "*" by default). Then
-%% It will try to match the path to one rule. If no rule match, then a 404 error
-%% is displayed.
-%%
-%% Once a rule is found we rewrite the request url using the "to" and
-%% "query" members. The identified token are matched to the rule and
-%% will replace var. if '*' is found in the rule it will contain the remaining
-%% part if it exists.
-%%
-%% Examples:
-%%
-%% Dispatch rule URL TO Tokens
-%%
-%% {"from": "/a/b", /a/b?k=v /some/b?k=v var =:= b
-%% "to": "/some/"} k = v
-%%
-%% {"from": "/a/b", /a/b /some/b?var=b var =:= b
-%% "to": "/some/:var"}
-%%
-%% {"from": "/a", /a /some
-%% "to": "/some/*"}
-%%
-%% {"from": "/a/*", /a/b/c /some/b/c
-%% "to": "/some/*"}
-%%
-%% {"from": "/a", /a /some
-%% "to": "/some/*"}
-%%
-%% {"from": "/a/:foo/*", /a/b/c /some/b/c?foo=b foo =:= b
-%% "to": "/some/:foo/*"}
-%%
-%% {"from": "/a/:foo", /a/b /some/?k=b&foo=b foo =:= b
-%% "to": "/some",
-%% "query": {
-%% "k": ":foo"
-%% }}
-%%
-%% {"from": "/a", /a?foo=b /some/b foo =:= b
-%% "to": "/some/:foo",
-%% }}
-
-
-
-handle_rewrite_req(#httpd{
- path_parts=[DbName, <<"_design">>, DesignName, _Rewrite|PathParts],
- method=Method,
- mochi_req=MochiReq}=Req, _Db, DDoc) ->
-
- % we are in a design handler
- DesignId = <<"_design/", DesignName/binary>>,
- Prefix = <<"/", (?l2b(couch_util:url_encode(DbName)))/binary, "/", DesignId/binary>>,
- QueryList = lists:map(fun decode_query_value/1, couch_httpd:qs(Req)),
-
- RewritesSoFar = erlang:get(?REWRITE_COUNT),
- MaxRewrites = list_to_integer(couch_config:get("httpd", "rewrite_limit", "100")),
- case RewritesSoFar >= MaxRewrites of
- true ->
- throw({bad_request, <<"Exceeded rewrite recursion limit">>});
- false ->
- erlang:put(?REWRITE_COUNT, RewritesSoFar + 1)
- end,
-
- #doc{body={Props}} = DDoc,
-
- % get rules from ddoc
- case couch_util:get_value(<<"rewrites">>, Props) of
- undefined ->
- couch_httpd:send_error(Req, 404, <<"rewrite_error">>,
- <<"Invalid path.">>);
- Bin when is_binary(Bin) ->
- couch_httpd:send_error(Req, 400, <<"rewrite_error">>,
- <<"Rewrite rules are a String. They must be a JSON Array.">>);
- Rules ->
- % create dispatch list from rules
- DispatchList = [make_rule(Rule) || {Rule} <- Rules],
- Method1 = couch_util:to_binary(Method),
-
- % get raw path by matching url to a rule. Throws not_found.
- {NewPathParts0, Bindings0} =
- try_bind_path(DispatchList, Method1, PathParts, QueryList),
- NewPathParts = [quote_plus(X) || X <- NewPathParts0],
- Bindings = maybe_encode_bindings(Bindings0),
-
- Path0 = string:join(NewPathParts, [?SEPARATOR]),
-
- % if path is relative detect it and rewrite path
- Path1 = case mochiweb_util:safe_relative_path(Path0) of
- undefined ->
- ?b2l(Prefix) ++ "/" ++ Path0;
- P1 ->
- ?b2l(Prefix) ++ "/" ++ P1
- end,
-
- Path2 = normalize_path(Path1),
-
- Path3 = case Bindings of
- [] ->
- Path2;
- _ ->
- [Path2, "?", mochiweb_util:urlencode(Bindings)]
- end,
-
- RawPath1 = ?b2l(iolist_to_binary(Path3)),
-
- % In order to do OAuth correctly, we have to save the
- % requested path. We use default so chained rewriting
- % wont replace the original header.
- Headers = mochiweb_headers:default("x-couchdb-requested-path",
- MochiReq:get(raw_path),
- MochiReq:get(headers)),
-
- ?LOG_DEBUG("rewrite to ~p ~n", [RawPath1]),
-
- % build a new mochiweb request
- MochiReq1 = mochiweb_request:new(MochiReq:get(socket),
- MochiReq:get(method),
- RawPath1,
- MochiReq:get(version),
- Headers),
-
- % cleanup, It force mochiweb to reparse raw uri.
- MochiReq1:cleanup(),
-
- #httpd{
- db_url_handlers = DbUrlHandlers,
- design_url_handlers = DesignUrlHandlers,
- default_fun = DefaultFun,
- url_handlers = UrlHandlers,
- user_ctx = UserCtx,
- auth = Auth
- } = Req,
-
- erlang:put(pre_rewrite_auth, Auth),
- erlang:put(pre_rewrite_user_ctx, UserCtx),
- couch_httpd:handle_request_int(MochiReq1, DefaultFun,
- UrlHandlers, DbUrlHandlers, DesignUrlHandlers)
- end.
-
-quote_plus({bind, X}) ->
- mochiweb_util:quote_plus(X);
-quote_plus(X) ->
- mochiweb_util:quote_plus(X).
-
-%% @doc Try to find a rule matching current url. If none is found
-%% 404 error not_found is raised
-try_bind_path([], _Method, _PathParts, _QueryList) ->
- throw(not_found);
-try_bind_path([Dispatch|Rest], Method, PathParts, QueryList) ->
- [{PathParts1, Method1}, RedirectPath, QueryArgs, Formats] = Dispatch,
- case bind_method(Method1, Method) of
- true ->
- case bind_path(PathParts1, PathParts, []) of
- {ok, Remaining, Bindings} ->
- Bindings1 = Bindings ++ QueryList,
- % we parse query args from the rule and fill
- % it eventually with bindings vars
- QueryArgs1 = make_query_list(QueryArgs, Bindings1,
- Formats, []),
- % remove params in QueryLists1 that are already in
- % QueryArgs1
- Bindings2 = lists:foldl(fun({K, V}, Acc) ->
- K1 = to_binding(K),
- KV = case couch_util:get_value(K1, QueryArgs1) of
- undefined -> [{K1, V}];
- _V1 -> []
- end,
- Acc ++ KV
- end, [], Bindings1),
-
- FinalBindings = Bindings2 ++ QueryArgs1,
- NewPathParts = make_new_path(RedirectPath, FinalBindings,
- Remaining, []),
- {NewPathParts, FinalBindings};
- fail ->
- try_bind_path(Rest, Method, PathParts, QueryList)
- end;
- false ->
- try_bind_path(Rest, Method, PathParts, QueryList)
- end.
-
-%% rewriting dynamically the quey list given as query member in
-%% rewrites. Each value is replaced by one binding or an argument
-%% passed in url.
-make_query_list([], _Bindings, _Formats, Acc) ->
- Acc;
-make_query_list([{Key, {Value}}|Rest], Bindings, Formats, Acc) ->
- Value1 = {Value},
- make_query_list(Rest, Bindings, Formats, [{to_binding(Key), Value1}|Acc]);
-make_query_list([{Key, Value}|Rest], Bindings, Formats, Acc) when is_binary(Value) ->
- Value1 = replace_var(Value, Bindings, Formats),
- make_query_list(Rest, Bindings, Formats, [{to_binding(Key), Value1}|Acc]);
-make_query_list([{Key, Value}|Rest], Bindings, Formats, Acc) when is_list(Value) ->
- Value1 = replace_var(Value, Bindings, Formats),
- make_query_list(Rest, Bindings, Formats, [{to_binding(Key), Value1}|Acc]);
-make_query_list([{Key, Value}|Rest], Bindings, Formats, Acc) ->
- make_query_list(Rest, Bindings, Formats, [{to_binding(Key), Value}|Acc]).
-
-replace_var(<<"*">>=Value, Bindings, Formats) ->
- get_var(Value, Bindings, Value, Formats);
-replace_var(<<":", Var/binary>> = Value, Bindings, Formats) ->
- get_var(Var, Bindings, Value, Formats);
-replace_var(Value, _Bindings, _Formats) when is_binary(Value) ->
- Value;
-replace_var(Value, Bindings, Formats) when is_list(Value) ->
- lists:reverse(lists:foldl(fun
- (<<":", Var/binary>>=Value1, Acc) ->
- [get_var(Var, Bindings, Value1, Formats)|Acc];
- (Value1, Acc) ->
- [Value1|Acc]
- end, [], Value));
-replace_var(Value, _Bindings, _Formats) ->
- Value.
-
-maybe_json(Key, Value) ->
- case lists:member(Key, [<<"key">>, <<"startkey">>, <<"start_key">>,
- <<"endkey">>, <<"end_key">>, <<"keys">>]) of
- true ->
- ?JSON_ENCODE(Value);
- false ->
- Value
- end.
-
-get_var(VarName, Props, Default, Formats) ->
- VarName1 = to_binding(VarName),
- Val = couch_util:get_value(VarName1, Props, Default),
- maybe_format(VarName, Val, Formats).
-
-maybe_format(VarName, Value, Formats) ->
- case couch_util:get_value(VarName, Formats) of
- undefined ->
- Value;
- Format ->
- format(Format, Value)
- end.
-
-format(<<"int">>, Value) when is_integer(Value) ->
- Value;
-format(<<"int">>, Value) when is_binary(Value) ->
- format(<<"int">>, ?b2l(Value));
-format(<<"int">>, Value) when is_list(Value) ->
- case (catch list_to_integer(Value)) of
- IntVal when is_integer(IntVal) ->
- IntVal;
- _ ->
- Value
- end;
-format(<<"bool">>, Value) when is_binary(Value) ->
- format(<<"bool">>, ?b2l(Value));
-format(<<"bool">>, Value) when is_list(Value) ->
- case string:to_lower(Value) of
- "true" -> true;
- "false" -> false;
- _ -> Value
- end;
-format(_Format, Value) ->
- Value.
-
-%% doc: build new patch from bindings. bindings are query args
-%% (+ dynamic query rewritten if needed) and bindings found in
-%% bind_path step.
-make_new_path([], _Bindings, _Remaining, Acc) ->
- lists:reverse(Acc);
-make_new_path([?MATCH_ALL], _Bindings, Remaining, Acc) ->
- Acc1 = lists:reverse(Acc) ++ Remaining,
- Acc1;
-make_new_path([?MATCH_ALL|_Rest], _Bindings, Remaining, Acc) ->
- Acc1 = lists:reverse(Acc) ++ Remaining,
- Acc1;
-make_new_path([{bind, P}|Rest], Bindings, Remaining, Acc) ->
- P2 = case couch_util:get_value({bind, P}, Bindings) of
- undefined -> << "undefined">>;
- P1 ->
- iolist_to_binary(P1)
- end,
- make_new_path(Rest, Bindings, Remaining, [P2|Acc]);
-make_new_path([P|Rest], Bindings, Remaining, Acc) ->
- make_new_path(Rest, Bindings, Remaining, [P|Acc]).
-
-
-%% @doc If method of the query fith the rule method. If the
-%% method rule is '*', which is the default, all
-%% request method will bind. It allows us to make rules
-%% depending on HTTP method.
-bind_method(?MATCH_ALL, _Method ) ->
- true;
-bind_method({bind, Method}, Method) ->
- true;
-bind_method(_, _) ->
- false.
-
-
-%% @doc bind path. Using the rule from we try to bind variables given
-%% to the current url by pattern matching
-bind_path([], [], Bindings) ->
- {ok, [], Bindings};
-bind_path([?MATCH_ALL], [Match|_RestMatch]=Rest, Bindings) ->
- {ok, Rest, [{?MATCH_ALL, Match}|Bindings]};
-bind_path(_, [], _) ->
- fail;
-bind_path([{bind, Token}|RestToken],[Match|RestMatch],Bindings) ->
- bind_path(RestToken, RestMatch, [{{bind, Token}, Match}|Bindings]);
-bind_path([Token|RestToken], [Token|RestMatch], Bindings) ->
- bind_path(RestToken, RestMatch, Bindings);
-bind_path(_, _, _) ->
- fail.
-
-
-%% normalize path.
-normalize_path(Path) ->
- "/" ++ string:join(normalize_path1(string:tokens(Path,
- "/"), []), [?SEPARATOR]).
-
-
-normalize_path1([], Acc) ->
- lists:reverse(Acc);
-normalize_path1([".."|Rest], Acc) ->
- Acc1 = case Acc of
- [] -> [".."|Acc];
- [T|_] when T =:= ".." -> [".."|Acc];
- [_|R] -> R
- end,
- normalize_path1(Rest, Acc1);
-normalize_path1(["."|Rest], Acc) ->
- normalize_path1(Rest, Acc);
-normalize_path1([Path|Rest], Acc) ->
- normalize_path1(Rest, [Path|Acc]).
-
-
-%% @doc transform json rule in erlang for pattern matching
-make_rule(Rule) ->
- Method = case couch_util:get_value(<<"method">>, Rule) of
- undefined -> ?MATCH_ALL;
- M -> to_binding(M)
- end,
- QueryArgs = case couch_util:get_value(<<"query">>, Rule) of
- undefined -> [];
- {Args} -> Args
- end,
- FromParts = case couch_util:get_value(<<"from">>, Rule) of
- undefined -> [?MATCH_ALL];
- From ->
- parse_path(From)
- end,
- ToParts = case couch_util:get_value(<<"to">>, Rule) of
- undefined ->
- throw({error, invalid_rewrite_target});
- To ->
- parse_path(To)
- end,
- Formats = case couch_util:get_value(<<"formats">>, Rule) of
- undefined -> [];
- {Fmts} -> Fmts
- end,
- [{FromParts, Method}, ToParts, QueryArgs, Formats].
-
-parse_path(Path) ->
- {ok, SlashRE} = re:compile(<<"\\/">>),
- path_to_list(re:split(Path, SlashRE), [], 0).
-
-%% @doc convert a path rule (from or to) to an erlang list
-%% * and path variable starting by ":" are converted
-%% in erlang atom.
-path_to_list([], Acc, _DotDotCount) ->
- lists:reverse(Acc);
-path_to_list([<<>>|R], Acc, DotDotCount) ->
- path_to_list(R, Acc, DotDotCount);
-path_to_list([<<"*">>|R], Acc, DotDotCount) ->
- path_to_list(R, [?MATCH_ALL|Acc], DotDotCount);
-path_to_list([<<"..">>|R], Acc, DotDotCount) when DotDotCount == 2 ->
- case couch_config:get("httpd", "secure_rewrites", "true") of
- "false" ->
- path_to_list(R, [<<"..">>|Acc], DotDotCount+1);
- _Else ->
- ?LOG_INFO("insecure_rewrite_rule ~p blocked", [lists:reverse(Acc) ++ [<<"..">>] ++ R]),
- throw({insecure_rewrite_rule, "too many ../.. segments"})
- end;
-path_to_list([<<"..">>|R], Acc, DotDotCount) ->
- path_to_list(R, [<<"..">>|Acc], DotDotCount+1);
-path_to_list([P|R], Acc, DotDotCount) ->
- P1 = case P of
- <<":", Var/binary>> ->
- to_binding(Var);
- _ -> P
- end,
- path_to_list(R, [P1|Acc], DotDotCount).
-
-maybe_encode_bindings([]) ->
- [];
-maybe_encode_bindings(Props) ->
- lists:foldl(fun
- ({{bind, <<"*">>}, _V}, Acc) ->
- Acc;
- ({{bind, K}, V}, Acc) ->
- V1 = iolist_to_binary(maybe_json(K, V)),
- [{K, V1}|Acc]
- end, [], Props).
-
-decode_query_value({K,V}) ->
- case lists:member(K, ["key", "startkey", "start_key",
- "endkey", "end_key", "keys"]) of
- true ->
- {to_binding(K), ?JSON_DECODE(V)};
- false ->
- {to_binding(K), ?l2b(V)}
- end.
-
-to_binding({bind, V}) ->
- {bind, V};
-to_binding(V) when is_list(V) ->
- to_binding(?l2b(V));
-to_binding(V) ->
- {bind, V}.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/c0855434/apps/couch_httpd/src/couch_httpd_stats_handlers.erl
----------------------------------------------------------------------
diff --git a/apps/couch_httpd/src/couch_httpd_stats_handlers.erl b/apps/couch_httpd/src/couch_httpd_stats_handlers.erl
deleted file mode 100644
index cd357ea..0000000
--- a/apps/couch_httpd/src/couch_httpd_stats_handlers.erl
+++ /dev/null
@@ -1,56 +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(couch_httpd_stats_handlers).
--include_lib("couch/include/couch_db.hrl").
-
--export([handle_stats_req/1]).
--import(couch_httpd, [
- send_json/2, send_json/3, send_json/4, send_method_not_allowed/2,
- start_json_response/2, send_chunk/2, end_json_response/1,
- start_chunked_response/3, send_error/4
-]).
-
-handle_stats_req(#httpd{method='GET', path_parts=[_]}=Req) ->
- flush(Req),
- send_json(Req, couch_stats_aggregator:all(range(Req)));
-
-handle_stats_req(#httpd{method='GET', path_parts=[_, _Mod]}) ->
- throw({bad_request, <<"Stat names must have exactly two parts.">>});
-
-handle_stats_req(#httpd{method='GET', path_parts=[_, Mod, Key]}=Req) ->
- flush(Req),
- Stats = couch_stats_aggregator:get_json({list_to_atom(binary_to_list(Mod)),
- list_to_atom(binary_to_list(Key))}, range(Req)),
- send_json(Req, {[{Mod, {[{Key, Stats}]}}]});
-
-handle_stats_req(#httpd{method='GET', path_parts=[_, _Mod, _Key | _Extra]}) ->
- throw({bad_request, <<"Stat names must have exactly two parts.">>});
-
-handle_stats_req(Req) ->
- send_method_not_allowed(Req, "GET").
-
-range(Req) ->
- case couch_util:get_value("range", couch_httpd:qs(Req)) of
- undefined ->
- 0;
- Value ->
- list_to_integer(Value)
- end.
-
-flush(Req) ->
- case couch_util:get_value("flush", couch_httpd:qs(Req)) of
- "true" ->
- couch_stats_aggregator:collect_sample();
- _Else ->
- ok
- end.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/c0855434/apps/couch_httpd/src/couch_httpd_sup.erl
----------------------------------------------------------------------
diff --git a/apps/couch_httpd/src/couch_httpd_sup.erl b/apps/couch_httpd/src/couch_httpd_sup.erl
deleted file mode 100644
index 1ce53f1..0000000
--- a/apps/couch_httpd/src/couch_httpd_sup.erl
+++ /dev/null
@@ -1,112 +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(couch_httpd_sup).
--behaviour(supervisor).
-
--export([start_link/0]).
--export([upgrade/0]).
--export([reload_listener/1,
- reload_listeners/0]).
-
-
-%% internal API
--export([init/1]).
--export([config_change/2]).
-
-
--spec start_link() -> ignore | {error, term()} | {ok, pid()}.
-start_link() ->
- {ok, Pid} = supervisor:start_link({local, ?MODULE}, ?MODULE, []),
-
- %% register to config events
- ok = couch_config:register(fun ?MODULE:config_change/2, Pid),
-
- %% display uris
- couch_httpd_util:display_uris(),
-
- %% write_uris
- couch_httpd_util:write_uri_file(),
-
- {ok, Pid}.
-
-
-%% @spec upgrade() -> ok
-%% @doc Add processes if necessary.
-upgrade() ->
- {ok, {_, Specs}} = init([]),
-
- Old = sets:from_list(
- [Name || {Name, _, _, _} <- supervisor:which_children(?MODULE)]),
- New = sets:from_list([Name || {Name, _, _, _, _, _} <- Specs]),
- Kill = sets:subtract(Old, New),
-
- sets:fold(fun (Id, ok) ->
- supervisor:terminate_child(?MODULE, Id),
- supervisor:delete_child(?MODULE, Id),
- ok
- end, ok, Kill),
-
- [supervisor:start_child(?MODULE, Spec) || Spec <- Specs],
- ok.
-
-%% @doc upgrade a listener
--spec reload_listener(atom()) -> {ok, pid()} | {error, term()}.
-reload_listener(Id) ->
- %% stop the listener and remove it from the supervision temporarely
- supervisor:terminate_child(?MODULE, Id),
- supervisor:delete_child(?MODULE, Id),
-
- %% restart the listener
- supervisor:start_child(?MODULE, listener_spec(Id)),
- couch_httpd_util:display_uris([Id]),
- ok.
-
-%% upgrade all listeners
--spec reload_listeners() -> ok.
-reload_listeners() ->
- [reload_listener(Id) || Id <- couch_httpd_util:get_listeners()],
- ok.
-
-
--spec init([]) -> {ok, {{one_for_one, 5, 10}, [supervisor:child_spec()]}}.
-init([]) ->
- Listeners = [listener_spec(Id) || Id <- couch_httpd_util:get_listeners()],
- Vhost = {couch_httpd_vhost,
- {couch_httpd_vhost, start_link, []},
- permanent, brutal_kill, worker, [couch_httpd_vhost]},
- {ok, {{one_for_one, 9, 10}, Listeners ++ [Vhost]}}.
-
-
-listener_spec(Id) ->
- {Id,
- {couch_httpd, start_link, [Id]},
- permanent, brutal_kill, worker, [couch_httpd]}.
-
-config_change("httpd", "bind_address") ->
- ?MODULE:reload_listeners();
-config_change("httpd", "port") ->
- ?MODULE:reload_listener(couch_http);
-config_change("httpd", "default_handler") ->
- ?MODULE:reload_listeners();
-config_change("httpd", "server_options") ->
- ?MODULE:reload_listeners();
-config_change("httpd", "socket_options") ->
- ?MODULE:reload_listeners();
-config_change("httpd", "authentication_handlers") ->
- couch_httpd:set_auth_handlers();
-config_change("httpd_global_handlers", _) ->
- ?MODULE:reload_listeners();
-config_change("httpd_db_handlers", _) ->
- ?MODULE:reload_listeners();
-config_change("ssl", _) ->
- ?MODULE:reload_listener(couch_https).
http://git-wip-us.apache.org/repos/asf/couchdb/blob/c0855434/apps/couch_httpd/src/couch_httpd_util.erl
----------------------------------------------------------------------
diff --git a/apps/couch_httpd/src/couch_httpd_util.erl b/apps/couch_httpd/src/couch_httpd_util.erl
deleted file mode 100644
index a3ba075..0000000
--- a/apps/couch_httpd/src/couch_httpd_util.erl
+++ /dev/null
@@ -1,80 +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(couch_httpd_util).
-
--export([display_uris/0, display_uris/1,
- write_uri_file/0,
- get_listeners/0,
- get_uri/2,
- get_scheme/1,
- get_port/1]).
-
--include_lib("couch/include/couch_db.hrl").
-
-
-display_uris() ->
- display_uris(get_listeners()).
-
-display_uris(Bindings) ->
- Ip = couch_config:get("httpd", "bind_address"),
- lists:foreach(fun(Binding) ->
- Uri = get_uri(Binding, Ip),
- ?LOG_INFO("HTTP API started on ~p~n", [Uri])
- end, Bindings).
-
-write_uri_file() ->
- Ip = couch_config:get("httpd", "bind_address"),
- Listeners = get_listeners(),
- Uris = [get_uri(Name, Ip) || Name <- Listeners],
- case couch_config:get("couchdb", "uri_file", null) of
- null -> ok;
- UriFile ->
- Lines = [begin case Uri of
- undefined -> [];
- Uri -> io_lib:format("~s~n", [Uri])
- end end || Uri <- Uris],
- case file:write_file(UriFile, Lines) of
- ok -> ok;
- {error, eacces} ->
- ?LOG_INFO("Permission error when writing to URI file ~s",
- [UriFile]),
- throw({file_permission_error, UriFile});
- Error2 ->
- ?LOG_INFO("Failed to write to URI file ~s: ~p~n",
- [UriFile, Error2]),
- throw(Error2)
- end
- end.
-
-get_listeners() ->
- SchemeStr = couch_config:get("httpd", "scheme", "http"),
- SchemeList = re:split(SchemeStr, "\\s*,\\s*",[{return, list}]),
-
- lists:foldl(fun(S, Acc) ->
- [list_to_atom("couch_" ++ S) | Acc]
- end, [], lists:reverse(SchemeList)).
-
-get_uri(Name, Ip) ->
- Port = get_port(Name),
- Scheme = get_scheme(Name),
- Scheme ++ "://" ++ Ip ++ ":" ++ integer_to_list(Port) ++ "/".
-
-get_scheme(couch_http) -> "http";
-get_scheme(couch_https) -> "https".
-
-get_port(Ref) ->
- try
- mochiweb_socket_server:get(Ref, port)
- catch
- exit:{noproc, _} -> undefined
- end.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/c0855434/apps/couch_httpd/src/couch_httpd_vhost.erl
----------------------------------------------------------------------
diff --git a/apps/couch_httpd/src/couch_httpd_vhost.erl b/apps/couch_httpd/src/couch_httpd_vhost.erl
deleted file mode 100644
index 258f4eb..0000000
--- a/apps/couch_httpd/src/couch_httpd_vhost.erl
+++ /dev/null
@@ -1,383 +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(couch_httpd_vhost).
--behaviour(gen_server).
-
--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]).
-
--include_lib("couch/include/couch_db.hrl").
-
--define(SEPARATOR, $\/).
--define(MATCH_ALL, {bind, '*'}).
-
--record(vhosts_state, {
- vhosts,
- vhost_globals,
- vhosts_fun}).
-
-%% 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.
-%%
-%% Declaration of vhosts take place in the configuration file :
-%%
-%% [vhosts]
-%% example.com = /example
-%% *.example.com = /example
-%%
-%% The first line will rewrite the rquest to display the content of the
-%% example database. This rule works only if the Host header is
-%% 'example.com' and won't work for CNAMEs. Second rule on the other hand
-%% match all CNAMES to example db. So www.example.com or db.example.com
-%% will work.
-%%
-%% The wildcard ('*') should always be the last in the cnames:
-%%
-%% "*.db.example.com = /" will match all cname on top of db
-%% examples to the root of the machine.
-%%
-%%
-%% Rewriting Hosts to path
-%% -----------------------
-%%
-%% Like in the _rewrite handler you could match some variable and use
-%them to create the target path. Some examples:
-%%
-%% [vhosts]
-%% *.example.com = /*
-%% :dbname.example.com = /:dbname
-%% :ddocname.:dbname.example.com = /:dbname/_design/:ddocname/_rewrite
-%%
-%% First rule pass wildcard as dbname, second do the same but use a
-%% variable name and the third one allows you to use any app with
-%% @ddocname in any db with @dbname .
-%%
-%% You could also change the default function to handle request by
-%% changing the setting `redirect_vhost_handler` in `httpd` section of
-%% the Ini:
-%%
-%% [httpd]
-%% redirect_vhost_handler = {Module, Fun}
-%%
-%% The function take 2 args : the mochiweb request object and the target
-%%% path.
-
-start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-
-%% @doc reload vhosts rules
-reload() ->
- gen_server:call(?MODULE, reload).
-
-get_state() ->
- gen_server:call(?MODULE, get_state).
-
-%% @doc Try to find a rule matching current Host heade. some rule is
-%% found it rewrite the Mochiweb Request else it return current Request.
-dispatch_host(MochiReq) ->
- #vhosts_state{
- vhost_globals = VHostGlobals,
- vhosts = VHosts,
- vhosts_fun=Fun} = get_state(),
-
- {"/" ++ VPath, Query, Fragment} = mochiweb_util:urlsplit_path(MochiReq:get(raw_path)),
- VPathParts = string:tokens(VPath, "/"),
-
- VHost = host(MochiReq),
- {VHostParts, VhostPort} = split_host_port(VHost),
- FinalMochiReq = case try_bind_vhost(VHosts, lists:reverse(VHostParts),
- VhostPort, VPathParts) of
- no_vhost_matched -> MochiReq;
- {VhostTarget, NewPath} ->
- case vhost_global(VHostGlobals, MochiReq) of
- true ->
- MochiReq;
- _Else ->
- NewPath1 = mochiweb_util:urlunsplit_path({NewPath, Query,
- Fragment}),
- MochiReq1 = mochiweb_request:new(MochiReq:get(socket),
- MochiReq:get(method),
- NewPath1,
- MochiReq:get(version),
- MochiReq:get(headers)),
- Fun(MochiReq1, VhostTarget)
- end
- end,
- FinalMochiReq.
-
-append_path("/"=_Target, "/"=_Path) ->
- "/";
-append_path(Target, Path) ->
- Target ++ Path.
-
-% 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,
- MochiReq:get(headers)),
-
- % build a new mochiweb request
- MochiReq1 = mochiweb_request:new(MochiReq:get(socket),
- MochiReq:get(method),
- Target,
- MochiReq:get(version),
- Headers),
- % cleanup, It force mochiweb to reparse raw uri.
- MochiReq1:cleanup(),
- 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
-%% keep available on vhosts. You can also use it to make databases 'global'.
-vhost_global( VhostGlobals, MochiReq) ->
- RawUri = MochiReq:get(raw_path),
- {"/" ++ Path, _, _} = mochiweb_util:urlsplit_path(RawUri),
-
- Front = case couch_httpd:partition(Path) of
- {"", "", ""} ->
- "/"; % Special case the root url handler
- {FirstPart, _, _} ->
- FirstPart
- end,
- [true] == [true||V <- VhostGlobals, V == Front].
-
-%% bind host
-%% first it try to bind the port then the hostname.
-try_bind_vhost([], _HostParts, _Port, _PathParts) ->
- no_vhost_matched;
-try_bind_vhost([VhostSpec|Rest], HostParts, Port, PathParts) ->
- {{VHostParts, VPort, VPath}, Path} = VhostSpec,
- case bind_port(VPort, Port) of
- 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 ->
- try_bind_vhost(Rest, HostParts, Port,
- PathParts)
- end;
- fail -> try_bind_vhost(Rest, HostParts, Port, PathParts)
- end;
- fail -> try_bind_vhost(Rest, HostParts, Port, PathParts)
- end.
-
-%% doc: build new patch from bindings. bindings are query args
-%% (+ dynamic query rewritten if needed) and bindings found in
-%% bind_path step.
-%% TODO: merge code with rewrite. But we need to make sure we are
-%% in string here.
-make_target([], _Bindings, _Remaining, Acc) ->
- lists:reverse(Acc);
-make_target([?MATCH_ALL], _Bindings, Remaining, Acc) ->
- Acc1 = lists:reverse(Acc) ++ Remaining,
- Acc1;
-make_target([?MATCH_ALL|_Rest], _Bindings, Remaining, Acc) ->
- Acc1 = lists:reverse(Acc) ++ Remaining,
- Acc1;
-make_target([{bind, P}|Rest], Bindings, Remaining, Acc) ->
- P2 = case couch_util:get_value({bind, P}, Bindings) of
- undefined -> "undefined";
- P1 -> P1
- end,
- make_target(Rest, Bindings, Remaining, [P2|Acc]);
-make_target([P|Rest], Bindings, Remaining, Acc) ->
- make_target(Rest, Bindings, Remaining, [P|Acc]).
-
-%% bind port
-bind_port(Port, Port) -> ok;
-bind_port('*', _) -> ok;
-bind_port(_,_) -> fail.
-
-%% bind bhost
-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(Rest, RestHost, [{{bind, Token}, Match}|Bindings]);
-bind_vhost([Cname|Rest], [Cname|RestHost], Bindings) ->
- bind_vhost(Rest, RestHost, Bindings);
-bind_vhost(_, _, _) -> fail.
-
-%% bind path
-bind_path([], PathParts) ->
- {ok, PathParts};
-bind_path(_VPathParts, []) ->
- fail;
-bind_path([Path|VRest],[Path|Rest]) ->
- bind_path(VRest, Rest);
-bind_path(_, _) ->
- fail.
-
-% utilities
-
-
-%% 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) ->
- Acc;
- ({Vhost, Path}, Acc) ->
- [{parse_vhost(Vhost), split_path(Path)}|Acc]
- end, [], couch_config:get("vhosts")),
-
- lists:reverse(lists:usort(Vhosts)).
-
-
-parse_vhost(Vhost) ->
- case urlsplit_netloc(Vhost, []) of
- {[], Path} ->
- {make_spec("*", []), '*', Path};
- {HostPort, []} ->
- {H, P} = split_host_port(HostPort),
- H1 = make_spec(H, []),
- {H1, P, []};
- {HostPort, Path} ->
- {H, P} = split_host_port(HostPort),
- 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,
- N+1, length(HostAsString)))) of
- {'EXIT', _} ->
- {split_host(HostAsString), '*'};
- Port ->
- {split_host(HostPart), Port}
- end
- end.
-
-split_host(HostAsString) ->
- string:tokens(HostAsString, "\.").
-
-split_path(Path) ->
- make_spec(string:tokens(Path, "/"), []).
-
-
-make_spec([], Acc) ->
- lists:reverse(Acc);
-make_spec([""|R], Acc) ->
- make_spec(R, Acc);
-make_spec(["*"|R], Acc) ->
- make_spec(R, [?MATCH_ALL|Acc]);
-make_spec([P|R], Acc) ->
- P1 = parse_var(P),
- make_spec(R, [P1|Acc]).
-
-
-parse_var(P) ->
- case P of
- ":" ++ Var ->
- {bind, Var};
- _ -> P
- end.
-
-
-% mochiweb doesn't export it.
-urlsplit_netloc("", Acc) ->
- {lists:reverse(Acc), ""};
-urlsplit_netloc(Rest=[C | _], Acc) when C =:= $/; C =:= $?; C =:= $# ->
- {lists:reverse(Acc), Rest};
-urlsplit_netloc([C | Rest], Acc) ->
- urlsplit_netloc(Rest, [C | Acc]).
-
-make_path(Parts) ->
- "/" ++ string:join(Parts,[?SEPARATOR]).
-
-init(_) ->
- ok = couch_config:register(fun ?MODULE:config_change/2),
-
- %% load configuration
- {VHostGlobals, VHosts, Fun} = load_conf(),
- State = #vhosts_state{
- vhost_globals=VHostGlobals,
- vhosts=VHosts,
- vhosts_fun=Fun},
- {ok, State}.
-
-handle_call(reload, _From, _State) ->
- {VHostGlobals, VHosts, Fun} = load_conf(),
- {reply, ok, #vhosts_state{
- vhost_globals=VHostGlobals,
- vhosts=VHosts,
- vhosts_fun=Fun}};
-handle_call(get_state, _From, State) ->
- {reply, State, State};
-handle_call(_Msg, _From, State) ->
- {noreply, State}.
-
-handle_cast(_Msg, State) ->
- {noreply, State}.
-
-handle_info(_Info, State) ->
- {noreply, State}.
-
-terminate(_Reason, _State) ->
- ok.
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-config_change("httpd", "vhost_global_handlers") ->
- ?MODULE:reload();
-config_change("httpd", "redirect_vhost_handler") ->
- ?MODULE:reload();
-config_change("vhosts", _) ->
- ?MODULE:reload().
-
-load_conf() ->
- %% get vhost globals
- VHostGlobals = re:split(couch_config:get("httpd",
- "vhost_global_handlers",""), "\\s*,\\s*",[{return, list}]),
-
- %% build vhosts matching rules
- VHosts = make_vhosts(),
-
- %% build vhosts handler fun
- DefaultVHostFun = "{couch_httpd_vhost, redirect_to_vhost}",
- Fun = couch_httpd:make_arity_2_fun(couch_config:get("httpd",
- "redirect_vhost_handler", DefaultVHostFun)),
-
- {VHostGlobals, VHosts, Fun}.