<commit_before>% 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_external).

-export([handle_external_req/2, handle_external_req/3]).
-export([send_external_response/2, json_req_obj/2]).
-export([default_or_content_type/2, parse_external_response/1]).

-import(chttpd,[send_error/4]).

-include("chttpd.hrl").

% handle_external_req/2
% for the old type of config usage:
% _external = {chttpd_external, handle_external_req}
% with urls like
% /db/_external/action/design/name
handle_external_req(#httpd{
                        path_parts=[_DbName, _External, UrlName | _Path]
                    }=HttpReq, Db) ->
    process_external_req(HttpReq, Db, UrlName);
handle_external_req(#httpd{path_parts=[_, _]}=Req, _Db) ->
    send_error(Req, 404, <<"external_server_error">>, <<"No server name specified.">>);
handle_external_req(Req, _) ->
    send_error(Req, 404, <<"external_server_error">>, <<"Broken assumption">>).

% handle_external_req/3
% for this type of config usage:
% _action = {chttpd_external, handle_external_req, <<"action">>}
% with urls like
% /db/_action/design/name
handle_external_req(HttpReq, Db, Name) ->
    process_external_req(HttpReq, Db, Name).

process_external_req(HttpReq, Db, Name) ->

    Response = couch_external_manager:execute(binary_to_list(Name),
        json_req_obj(HttpReq, Db)),

    case Response of
    {unknown_external_server, Msg} ->
        send_error(HttpReq, 404, <<"external_server_error">>, Msg);
    _ ->
        send_external_response(HttpReq, Response)
    end.

json_req_obj(#httpd{mochi_req=Req,
               method=Verb,
               path_parts=Path,
               req_body=ReqBody
            } = HttpReq, Db) ->
    Body = case ReqBody of
        undefined -> Req:recv_body();
        Else -> Else
    end,
    ParsedForm = case Req:get_primary_header_value("content-type") of
        "application/x-www-form-urlencoded" ++ _ ->
            mochiweb_util:parse_qs(Body);
        _ ->
            []
    end,
    Headers = Req:get(headers),
    Hlist = mochiweb_headers:to_list(Headers),
    {ok, Info} = fabric:get_db_info(Db),

    % send correct path to customer - BugzID 6849
    CustomerBin = list_to_binary(cloudant_util:customer_name(HttpReq)),
    Len = byte_size(CustomerBin),
    FixedPath = case Path of
    [<<CustomerBin:Len/binary, "/", DbName/binary>> | Rest] ->
        [DbName | Rest];
    NoCustomer ->
        NoCustomer
    end,

    % add headers...
    {[{<<"info">>, {cloudant_util:customer_db_info(HttpReq, Info)}},
        {<<"verb">>, Verb},
        {<<"path">>, FixedPath},
        {<<"query">>, to_json_terms(Req:parse_qs())},
        {<<"headers">>, to_json_terms(Hlist)},
        {<<"body">>, Body},
        {<<"form">>, to_json_terms(ParsedForm)},
        {<<"cookie">>, to_json_terms(Req:parse_cookie())},
        {<<"userCtx">>, couch_util:json_user_ctx(Db)}]}.

to_json_terms(Data) ->
    to_json_terms(Data, []).
to_json_terms([], Acc) ->
    {lists:reverse(Acc)};
to_json_terms([{Key, Value} | Rest], Acc) when is_atom(Key) ->
    to_json_terms(Rest, [{list_to_binary(atom_to_list(Key)), list_to_binary(Value)} | Acc]);
to_json_terms([{Key, Value} | Rest], Acc) ->
    to_json_terms(Rest, [{list_to_binary(Key), list_to_binary(Value)} | Acc]).


send_external_response(#httpd{mochi_req=MochiReq}, Response) ->
    #extern_resp_args{
        code = Code,
        data = Data,
        ctype = CType,
        headers = Headers
    } = parse_external_response(Response),
    Resp = MochiReq:respond({Code,
        default_or_content_type(CType, Headers ++ chttpd:server_header()), Data}),
    {ok, Resp}.

parse_external_response({Response}) ->
    lists:foldl(fun({Key,Value}, Args) -><commit_msg>add json_req_obj/3, which accepts a docid as last argument<commit_after>% 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_external).

-export([handle_external_req/2, handle_external_req/3]).
-export([send_external_response/2, json_req_obj/2, json_req_obj/3]).
-export([default_or_content_type/2, parse_external_response/1]).

-import(chttpd,[send_error/4]).

-include("chttpd.hrl").

% handle_external_req/2
% for the old type of config usage:
% _external = {chttpd_external, handle_external_req}
% with urls like
% /db/_external/action/design/name
handle_external_req(#httpd{
                        path_parts=[_DbName, _External, UrlName | _Path]
                    }=HttpReq, Db) ->
    process_external_req(HttpReq, Db, UrlName);
handle_external_req(#httpd{path_parts=[_, _]}=Req, _Db) ->
    send_error(Req, 404, <<"external_server_error">>, <<"No server name specified.">>);
handle_external_req(Req, _) ->
    send_error(Req, 404, <<"external_server_error">>, <<"Broken assumption">>).

% handle_external_req/3
% for this type of config usage:
% _action = {chttpd_external, handle_external_req, <<"action">>}
% with urls like
% /db/_action/design/name
handle_external_req(HttpReq, Db, Name) ->
    process_external_req(HttpReq, Db, Name).

process_external_req(HttpReq, Db, Name) ->

    Response = couch_external_manager:execute(binary_to_list(Name),
        json_req_obj(HttpReq, Db)),

    case Response of
    {unknown_external_server, Msg} ->
        send_error(HttpReq, 404, <<"external_server_error">>, Msg);
    _ ->
        send_external_response(HttpReq, Response)
    end.

json_req_obj(Req, Db) -> json_req_obj(Req, Db, null).
json_req_obj(#httpd{mochi_req=Req,
               method=Method,
               path_parts=Path,
               req_body=ReqBody
            } = HttpReq, Db, DocId) ->
    Body = case ReqBody of
        undefined -> Req:recv_body();
        Else -> Else
    end,
    ParsedForm = case Req:get_primary_header_value("content-type") of
        "application/x-www-form-urlencoded" ++ _ ->
            mochiweb_util:parse_qs(Body);
        _ ->
            []
    end,
    Headers = Req:get(headers),
    Hlist = mochiweb_headers:to_list(Headers),
    {ok, Info} = fabric:get_db_info(Db),

    % send correct path to customer - BugzID 6849
    CustomerBin = list_to_binary(cloudant_util:customer_name(HttpReq)),
    Len = byte_size(CustomerBin),
    FixedPath = case Path of
    [<<CustomerBin:Len/binary, "/", DbName/binary>> | Rest] ->
        [DbName | Rest];
    NoCustomer ->
        NoCustomer
    end,
    % add headers...
    {[{<<"info">>, {cloudant_util:customer_db_info(HttpReq, Info)}},
        {<<"id">>, DocId},
        {<<"method">>, Method},
        {<<"path">>, FixedPath},
        {<<"query">>, json_query_keys(to_json_terms(Req:parse_qs()))},
        {<<"headers">>, to_json_terms(Hlist)},
        {<<"body">>, Body},
        {<<"peer">>, ?l2b(Req:get(peer))},
        {<<"form">>, to_json_terms(ParsedForm)},
        {<<"cookie">>, to_json_terms(Req:parse_cookie())},
        {<<"userCtx">>, couch_util:json_user_ctx(Db#db{name=hd(FixedPath)})}]}.

to_json_terms(Data) ->
    to_json_terms(Data, []).
to_json_terms([], Acc) ->
    {lists:reverse(Acc)};
to_json_terms([{Key, Value} | Rest], Acc) when is_atom(Key) ->
    to_json_terms(Rest, [{list_to_binary(atom_to_list(Key)), list_to_binary(Value)} | Acc]);
to_json_terms([{Key, Value} | Rest], Acc) ->
    to_json_terms(Rest, [{list_to_binary(Key), list_to_binary(Value)} | Acc]).

json_query_keys({Json}) ->
    json_query_keys(Json, []).
json_query_keys([], Acc) ->
    {lists:reverse(Acc)};
json_query_keys([{<<"startkey">>, Value} | Rest], Acc) ->
    json_query_keys(Rest, [{<<"startkey">>, couch_util:json_decode(Value)}|Acc]);
json_query_keys([{<<"endkey">>, Value} | Rest], Acc) ->
    json_query_keys(Rest, [{<<"endkey">>, couch_util:json_decode(Value)}|Acc]);
json_query_keys([{<<"key">>, Value} | Rest], Acc) ->
    json_query_keys(Rest, [{<<"key">>, couch_util:json_decode(Value)}|Acc]);
json_query_keys([Term | Rest], Acc) ->
    json_query_keys(Rest, [Term|Acc]).

send_external_response(#httpd{mochi_req=MochiReq}, Response) ->
    #extern_resp_args{
        code = Code,
        data = Data,
        ctype = CType,
        headers = Headers
    } = parse_external_response(Response),
    Resp = MochiReq:respond({Code,
        default_or_content_type(CType, Headers ++ chttpd:server_header()), Data}),
    {ok, Resp}.

parse_external_response({Response}) ->
    lists:foldl(fun({Key,Value}, Args) ->
{'fraction_non_alphanumeric': 0.15420409046639974, 'fraction_numerical': 0.005951736825018938, 'mean_word_length': 3.798546209761163, 'pattern_counts': {'":': 0, '<': 83, '<?xml version=': 0, '>': 132, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6402670', 'n_tokens_mistral': 3218, 'n_tokens_neox': 3114, 'n_words': 626}