summaryrefslogtreecommitdiff
path: root/src/couch/src/couch_httpd_vhost.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/couch/src/couch_httpd_vhost.erl')
-rw-r--r--src/couch/src/couch_httpd_vhost.erl457
1 files changed, 0 insertions, 457 deletions
diff --git a/src/couch/src/couch_httpd_vhost.erl b/src/couch/src/couch_httpd_vhost.erl
deleted file mode 100644
index 0bff6a36d..000000000
--- a/src/couch/src/couch_httpd_vhost.erl
+++ /dev/null
@@ -1,457 +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).
--vsn(1).
--behaviour(config_listener).
-
--compile(tuple_calls).
-
--export([start_link/0, 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]).
-
-% config_listener api
--export([handle_config_change/5, handle_config_terminate/3]).
-
--include_lib("couch/include/couch_db.hrl").
-
--define(SEPARATOR, $\/).
--define(MATCH_ALL, {bind, '*'}).
--define(RELISTEN_DELAY, 5000).
-
--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) ->
- case vhost_enabled() of
- true ->
- dispatch_host_int(MochiReq);
- false ->
- MochiReq
- end.
-
-dispatch_host_int(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),
-
- couch_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 = chttpd_util:get_chttpd_config(
- "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,
- [],
- 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 = config:listen_for_changes(?MODULE, nil),
-
- %% 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(restart_config_listener, State) ->
- ok = config:listen_for_changes(?MODULE, nil),
- {noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
-
-terminate(_Reason, _State) ->
- ok.
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-handle_config_change("vhosts", _, _, _, _) ->
- {ok, ?MODULE:reload()};
-handle_config_change(_, _, _, _, _) ->
- {ok, nil}.
-
-handle_config_terminate(_, stop, _) ->
- ok;
-handle_config_terminate(_Server, _Reason, _State) ->
- erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener).
-
-load_conf() ->
- %% get vhost globals
- VHostGlobals = re:split(
- "_utils, _uuids, _session, _users",
- "\\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(DefaultVHostFun),
-
- {VHostGlobals, VHosts, Fun}.
-
-%% cheaply determine if there are any virtual hosts
-%% configured at all.
-vhost_enabled() ->
- case config:get("vhosts") of
- [] ->
- false;
- _ ->
- true
- end.