From ec75b49cc9385495d6fa40cac50f825def9433d8 Mon Sep 17 00:00:00 2001 From: Benoit Chesneau Date: Tue, 7 Sep 2010 23:35:28 +0000 Subject: fix issue #COUCHDB-230 . now it's possible to do */test = /db/_design/test or even example.com/test = /db/_design/test and other stuff already possible with vhost manager. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@993558 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_httpd_vhost.erl | 85 ++++++++++++++++++++++++++++++--------- test/etap/160-vhosts.t | 57 ++++++++++++++++++++++++-- 2 files changed, 121 insertions(+), 21 deletions(-) diff --git a/src/couchdb/couch_httpd_vhost.erl b/src/couchdb/couch_httpd_vhost.erl index 06bb95c2..3dba2919 100644 --- a/src/couchdb/couch_httpd_vhost.erl +++ b/src/couchdb/couch_httpd_vhost.erl @@ -17,7 +17,7 @@ -export([start_link/0, init/1, handle_call/3, handle_info/2, handle_cast/2]). -export([code_change/3, terminate/2]). --export([match_vhost/1]). +-export([match_vhost/1, urlsplit_netloc/2]). -export([redirect_to_vhost/2]). -include("couch_db.hrl"). @@ -149,6 +149,8 @@ handle_call({match_vhost, MochiReq}, _From, State) -> vhost_fun = Fun } = State, + {"/" ++ VPath, Query, Fragment} = mochiweb_util:urlsplit_path(MochiReq:get(raw_path)), + VPathParts = string:tokens(VPath, "/"), XHost = couch_config:get("httpd", "x_forwarded_host", "X-Forwarded-Host"), VHost = case MochiReq:get_header_value(XHost) of @@ -160,18 +162,25 @@ handle_call({match_vhost, MochiReq}, _From, State) -> Value -> Value end, {VHostParts, VhostPort} = split_host_port(VHost), - MochiReq1 = case try_bind_vhost(VHosts, lists:reverse(VHostParts), - VhostPort) of + FinalMochiReq = case try_bind_vhost(VHosts, lists:reverse(VHostParts), + VhostPort, VPathParts) of no_vhost_matched -> MochiReq; - VhostTarget -> + {VhostTarget, NewPath} -> case vhost_global(VHostGlobals, MochiReq) of true -> MochiReq; _Else -> - Fun(MochiReq, VhostTarget) + 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, - {reply, {ok, MochiReq1}, State}; + {reply, {ok, FinalMochiReq}, State}; % update vhosts handle_call(vhosts_changed, _From, State) -> @@ -247,19 +256,25 @@ vhost_global( VhostGlobals, MochiReq) -> %% bind host %% first it try to bind the port then the hostname. -try_bind_vhost([], _HostParts, _Port) -> +try_bind_vhost([], _HostParts, _Port, _PathParts) -> no_vhost_matched; -try_bind_vhost([VhostSpec|Rest], HostParts, Port) -> - {{VHostParts, VPort}, Path} = VhostSpec, +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} -> - Path1 = make_target(Path, Bindings, Remainings, []), - "/" ++ string:join(Path1,[?SEPARATOR]); - fail -> try_bind_vhost(Rest, HostParts, Port) + {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) + fail -> try_bind_vhost(Rest, HostParts, Port, PathParts) end. %% doc: build new patch from bindings. bindings are query args @@ -288,7 +303,7 @@ make_target([P|Rest], Bindings, Remaining, Acc) -> bind_port(Port, Port) -> ok; bind_port(_,_) -> fail. -% bind bhost +%% bind bhost bind_vhost([],[], Bindings) -> {ok, Bindings, []}; bind_vhost([?MATCH_ALL], [], _Bindings) -> fail; bind_vhost([?MATCH_ALL], Rest, Bindings) -> {ok, Bindings, Rest}; @@ -299,18 +314,41 @@ 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 make_vhosts() -> lists:foldl(fun({Vhost, Path}, Acc) -> - {H, P} = split_host_port(Vhost), - H1 = make_spec(H, []), - [{{H1,P}, split_path(Path)}|Acc] + [{parse_vhost(Vhost), split_path(Path)}|Acc] end, [], couch_config:get("vhosts")). +parse_vhost(Vhost) -> + case urlsplit_netloc(Vhost, []) of + {[], Path} -> + {make_spec("*", []), 80, 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 -> @@ -351,3 +389,14 @@ parse_var(P) -> _ -> 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]). diff --git a/test/etap/160-vhosts.t b/test/etap/160-vhosts.t index 7eaf2874..aea2e829 100755 --- a/test/etap/160-vhosts.t +++ b/test/etap/160-vhosts.t @@ -54,7 +54,7 @@ config_files() -> main(_) -> test_util:init_code_path(), - etap:plan(10), + etap:plan(14), case (catch test()) of ok -> etap:end_tests(); @@ -105,13 +105,18 @@ test() -> ok = couch_config:set("vhosts", "example.com", "/etap-test-db", false), ok = couch_config:set("vhosts", "*.example.com", "/etap-test-db/_design/doc1/_rewrite", false), + ok = couch_config:set("vhosts", "example.com/test", "/etap-test-db", false), ok = couch_config:set("vhosts", "example1.com", "/etap-test-db/_design/doc1/_rewrite/", false), ok = couch_config:set("vhosts",":appname.:dbname.example1.com", "/:dbname/_design/:appname/_rewrite/", false), ok = couch_config:set("vhosts", ":dbname.example1.com", "/:dbname", false), + ok = couch_config:set("vhosts", "*.example2.com", "/*", false), - + ok = couch_config:set("vhosts", "*/test", "/etap-test-db", false), + ok = couch_config:set("vhosts", "*.example2.com/test", "/*", false), + ok = couch_config:set("vhosts", "*/test1", + "/etap-test-db/_design/doc1/_show/test", false), test_regular_request(), test_vhost_request(), @@ -123,7 +128,11 @@ test() -> test_vhost_request_replace_var(), test_vhost_request_replace_var1(), test_vhost_request_replace_wildcard(), - + test_vhost_request_path(), + test_vhost_request_path1(), + test_vhost_request_path2(), + test_vhost_request_path3(), + %% restart boilerplate couch_db:close(Db), timer:sleep(3000), @@ -231,3 +240,45 @@ test_vhost_request_replace_wildcard() -> etap:is(true, true, "should return database info"); _Else -> false end. + +test_vhost_request_path() -> + Uri = server() ++ "test", + case ibrowse:send_req(Uri, [], get, [], [{host_header, "example.com"}]) of + {ok, _, _, Body} -> + {[{<<"db_name">>, <<"etap-test-db">>},_,_,_,_,_,_,_,_,_]} + = couch_util:json_decode(Body), + etap:is(true, true, "should return database info"); + _Else -> false + end. + +test_vhost_request_path1() -> + Url = server() ++ "test/doc1?revs_info=true", + case ibrowse:send_req(Url, [], get, [], []) of + {ok, _, _, Body} -> + {JsonProps} = couch_util:json_decode(Body), + HasRevsInfo = proplists:is_defined(<<"_revs_info">>, JsonProps), + etap:is(HasRevsInfo, true, "should return _revs_info"); + _Else -> false + end. + +test_vhost_request_path2() -> + Uri = server() ++ "test", + case ibrowse:send_req(Uri, [], get, [], [{host_header,"etap-test-db.example2.com"}]) of + {ok, _, _, Body} -> + {[{<<"db_name">>, <<"etap-test-db">>},_,_,_,_,_,_,_,_,_]} + = couch_util:json_decode(Body), + etap:is(true, true, "should return database info"); + _Else -> false + end. + +test_vhost_request_path3() -> + Uri = server() ++ "test1", + case ibrowse:send_req(Uri, [], get, [], []) of + {ok, _, _, Body} -> + {Json} = couch_util:json_decode(Body), + etap:is(case proplists:get_value(<<"path">>, Json) of + <<"/etap-test-db/_design/doc1/_show/test">> -> true; + _ -> false + end, true, <<"path in req ok">>); + _Else -> false + end. -- cgit v1.2.3