summaryrefslogtreecommitdiff
path: root/apps/couch/src/couch_httpd_rewrite.erl
diff options
context:
space:
mode:
Diffstat (limited to 'apps/couch/src/couch_httpd_rewrite.erl')
-rw-r--r--apps/couch/src/couch_httpd_rewrite.erl189
1 files changed, 112 insertions, 77 deletions
diff --git a/apps/couch/src/couch_httpd_rewrite.erl b/apps/couch/src/couch_httpd_rewrite.erl
index 6c3d0e3c..8480c1e9 100644
--- a/apps/couch/src/couch_httpd_rewrite.erl
+++ b/apps/couch/src/couch_httpd_rewrite.erl
@@ -117,8 +117,7 @@ handle_rewrite_req(#httpd{
% we are in a design handler
DesignId = <<"_design/", DesignName/binary>>,
Prefix = <<"/", DbName/binary, "/", DesignId/binary>>,
- QueryList = couch_httpd:qs(Req),
- QueryList1 = [{to_binding(K), V} || {K, V} <- QueryList],
+ QueryList = lists:map(fun decode_query_value/1, couch_httpd:qs(Req)),
#doc{body={Props}} = DDoc,
@@ -133,10 +132,11 @@ handle_rewrite_req(#httpd{
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.
- RawPath = case try_bind_path(DispatchList, couch_util:to_binary(Method), PathParts,
- QueryList1) of
+ RawPath = case try_bind_path(DispatchList, Method1,
+ PathParts, QueryList) of
no_dispatch_path ->
throw(not_found);
{NewPathParts, Bindings} ->
@@ -144,12 +144,13 @@ handle_rewrite_req(#httpd{
% build new path, reencode query args, eventually convert
% them to json
- Path = lists:append(
- string:join(Parts, [?SEPARATOR]),
- case Bindings of
- [] -> [];
- _ -> [$?, encode_query(Bindings)]
- end),
+ Bindings1 = maybe_encode_bindings(Bindings),
+ Path = binary_to_list(
+ iolist_to_binary([
+ string:join(Parts, [?SEPARATOR]),
+ [["?", mochiweb_util:urlencode(Bindings1)]
+ || Bindings1 =/= [] ]
+ ])),
% if path is relative detect it and rewrite path
case mochiweb_util:safe_relative_path(Path) of
@@ -196,7 +197,7 @@ quote_plus(X) ->
try_bind_path([], _Method, _PathParts, _QueryList) ->
no_dispatch_path;
try_bind_path([Dispatch|Rest], Method, PathParts, QueryList) ->
- [{PathParts1, Method1}, RedirectPath, QueryArgs] = Dispatch,
+ [{PathParts1, Method1}, RedirectPath, QueryArgs, Formats] = Dispatch,
case bind_method(Method1, Method) of
true ->
case bind_path(PathParts1, PathParts, []) of
@@ -204,7 +205,8 @@ try_bind_path([Dispatch|Rest], Method, PathParts, QueryList) ->
Bindings1 = Bindings ++ QueryList,
% we parse query args from the rule and fill
% it eventually with bindings vars
- QueryArgs1 = make_query_list(QueryArgs, Bindings1, []),
+ QueryArgs1 = make_query_list(QueryArgs, Bindings1,
+ Formats, []),
% remove params in QueryLists1 that are already in
% QueryArgs1
Bindings2 = lists:foldl(fun({K, V}, Acc) ->
@@ -230,56 +232,79 @@ try_bind_path([Dispatch|Rest], Method, PathParts, QueryList) ->
%% 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, Acc) ->
+make_query_list([], _Bindings, _Formats, Acc) ->
Acc;
-make_query_list([{Key, {Value}}|Rest], Bindings, Acc) ->
- Value1 = to_json({Value}),
- make_query_list(Rest, Bindings, [{to_binding(Key), Value1}|Acc]);
-make_query_list([{Key, Value}|Rest], Bindings, Acc) when is_binary(Value) ->
- Value1 = replace_var(Key, Value, Bindings),
- make_query_list(Rest, Bindings, [{to_binding(Key), Value1}|Acc]);
-make_query_list([{Key, Value}|Rest], Bindings, Acc) when is_list(Value) ->
- Value1 = replace_var(Key, Value, Bindings),
- make_query_list(Rest, Bindings, [{to_binding(Key), Value1}|Acc]);
-make_query_list([{Key, Value}|Rest], Bindings, Acc) ->
- make_query_list(Rest, Bindings, [{to_binding(Key), Value}|Acc]).
-
-replace_var(Key, Value, Bindings) ->
- case Value of
- <<":", Var/binary>> ->
- get_var(Var, Bindings, Value);
- _ when is_list(Value) ->
- Value1 = lists:foldr(fun(V, Acc) ->
- V1 = case V of
- <<":", VName/binary>> ->
- case get_var(VName, Bindings, V) of
- V2 when is_list(V2) ->
- iolist_to_binary(V2);
- V2 -> V2
- end;
- _ ->
-
- V
- end,
- [V1|Acc]
- end, [], Value),
- to_json(Value1);
- _ when is_binary(Value) ->
- Value;
- _ ->
- case Key of
- <<"key">> -> to_json(Value);
- <<"startkey">> -> to_json(Value);
- <<"endkey">> -> to_json(Value);
- _ ->
- lists:flatten(?JSON_ENCODE(Value))
- end
+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) ->
+get_var(VarName, Props, Default, Formats) ->
VarName1 = to_binding(VarName),
- couch_util:get_value(VarName1, Props, Default).
+ 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
@@ -295,7 +320,8 @@ make_new_path([?MATCH_ALL|_Rest], _Bindings, Remaining, Acc) ->
make_new_path([{bind, P}|Rest], Bindings, Remaining, Acc) ->
P2 = case couch_util:get_value({bind, P}, Bindings) of
undefined -> << "undefined">>;
- P1 -> P1
+ P1 ->
+ iolist_to_binary(P1)
end,
make_new_path(Rest, Bindings, Remaining, [P2|Acc]);
make_new_path([P|Rest], Bindings, Remaining, Acc) ->
@@ -306,7 +332,7 @@ make_new_path([P|Rest], Bindings, Remaining, Acc) ->
%% 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) ->
+bind_method(?MATCH_ALL, _Method ) ->
true;
bind_method({bind, Method}, Method) ->
true;
@@ -318,8 +344,8 @@ bind_method(_, _) ->
%% to the current url by pattern matching
bind_path([], [], Bindings) ->
{ok, [], Bindings};
-bind_path([?MATCH_ALL], Rest, Bindings) when is_list(Rest) ->
- {ok, Rest, 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) ->
@@ -372,7 +398,11 @@ make_rule(Rule) ->
To ->
parse_path(To)
end,
- [{FromParts, Method}, ToParts, QueryArgs].
+ 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(<<"\\/">>),
@@ -405,17 +435,25 @@ path_to_list([P|R], Acc, DotDotCount) ->
end,
path_to_list(R, [P1|Acc], DotDotCount).
-encode_query(Props) ->
- Props1 = lists:foldl(fun ({{bind, K}, V}, Acc) ->
- V1 = case is_list(V) orelse is_binary(V) of
- true -> V;
- false ->
- % probably it's a number
- quote_plus(V)
- end,
- [{K, V1} | Acc]
- end, [], Props),
- lists:flatten(mochiweb_util:urlencode(Props1)).
+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};
@@ -423,6 +461,3 @@ to_binding(V) when is_list(V) ->
to_binding(?l2b(V));
to_binding(V) ->
{bind, V}.
-
-to_json(V) ->
- iolist_to_binary(?JSON_ENCODE(V)).