<commit_before>%% -*- erlang-indent-level: 4;indent-tabs-mode: nil; fill-column: 92 -*-
%% ex: ts=4 sw=4 et
%% @author <NAME>
%% @author <NAME> <<EMAIL>>
%% @author <NAME> <<EMAIL>>
%% @author <NAME>
%% Copyright 2011-2015 Chef Software, Inc. All Rights Reserved.
%%
%% This file is provided to you 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(chef_wm_util).

-export([base_uri/1,
         environment_not_found_message/1,
         error_message_envelope/1,
         extract_from_path/2,
         get_header_fun/2,
         fetch_org_metadata/1,
         base_mods/0,
         not_found_message/2,
         num_versions/1,
         num_versions/2,
         object_name/2,
         set_json_body/2,
         set_uri_of_created_resource/2,
         set_location_of_created_resource/2,
         with_error_body/2
        ]).

-include("oc_chef_wm.hrl").

%% TODO: These types are just placeholders until we get all the types cleaned up
-type ejson() :: {[tuple()]}.

%% @doc Report the names of the "pluggable" modules determined at compile time. Useful for
%% debugging builds.
base_mods() ->
    [{base_resource, oc_chef_wm}, {base_routes, oc_chef_wm_routes}].

%% @doc Returns the base URI for the server as called by the client as a string.
%% By default, use the Host: header value, or if configured with the atom 'host_header'
%% Otherwise, use the configured url.
-spec base_uri(wm_req()) -> string().
base_uri(Req) ->
    case application:get_env(oc_chef_wm, base_resource_url) of
        undefined         -> scheme(Req) ++ "://" ++ vhost(Req);
        {ok, host_header} -> scheme(Req) ++ "://" ++ vhost(Req);
        {ok, ExternalUrl} -> ExternalUrl
    end.

%% @doc Returns the host from the request headers, or use the webmachine default
vhost(Req) ->
    case wrq:get_req_header("host", Req) of
        B when is_binary(B) -> binary_to_list(B);
        S when is_list(S) -> S;
        undefined -> fqdn_with_port(Req)
    end.

%% @doc Returns the host via webmachine
fqdn_with_port(Req) ->
    string:join(wrq:host_tokens(Req), ".") ++ port_string(wrq:port(Req)).

get_header_fun(Req, State = #base_state{header_fun = undefined}) ->
    GetHeader = fun(H) ->
                        Name = case is_binary(H) of
                                   true -> binary_to_list(H);
                                   false -> H
                               end,
                        case wrq:get_req_header(string:to_lower(Name), Req) of
                            B when is_binary(B) -> B;
                            "" -> undefined; %% We want to treat empty header values as missing
                            % Possible DOS attack if this header includes a massive string and possibility
                            % of exhausting the heap?
                            S when is_list(S) -> iolist_to_binary(S);
                            undefined -> undefined
                        end
                end,
    {GetHeader, State#base_state{header_fun = GetHeader}};
get_header_fun(_Req, State) ->
    {State#base_state.header_fun, State}.

fetch_org_metadata(#base_state{organization_name = undefined}) ->
    {undefined, undefined};
fetch_org_metadata(#base_state{organization_guid = Id, organization_authz_id=AuthzId}) when is_binary(Id) ->
    {Id, AuthzId};
fetch_org_metadata(#base_state{organization_guid = undefined,
                               organization_name = OrgName,
                               chef_db_context = DbContext}) ->
    case chef_db:fetch_org_metadata(DbContext, OrgName) of
        not_found -> throw({org_not_found, OrgName});
        {Guid, AuthzId} -> {Guid, AuthzId}
    end.

-spec environment_not_found_message( bin_or_string() ) -> ejson().
environment_not_found_message(EnvName) ->
    error_message_envelope(iolist_to_binary([<<"Cannot load environment '">>, EnvName, <<"'">>])).

%% TODO: Why are these messages phrased differently?  This is what the Ruby endpoints were
%% doing, FYI.  Is this something we're stuck with, or can we update the API and normalize
%% these messages?
-spec not_found_message( node | role | data_bag | data_bag_item1 |
                         data_bag_item2 | client | data_bag_missing_for_item_post |
                         environment | sandbox | sandboxes | cookbook |
                         cookbook_version | user | invitation | policy | policy_group,
                         bin_or_string() | {bin_or_string(), bin_or_string()} ) -> ejson().
not_found_message(node, Name) ->
    error_message_envelope(iolist_to_binary(["node '", Name, "' not found"]));
not_found_message(role, Name) ->
    error_message_envelope(iolist_to_binary(["Cannot load role ", Name]));
not_found_message(data_bag, Name) ->
    error_message_envelope(iolist_to_binary(["Cannot load data bag ", Name]));
not_found_message(data_bag_item1, {BagName, ItemName}) ->
    error_message_envelope(iolist_to_binary(["Cannot load data bag ", BagName, " item ",
                                             ItemName]));
not_found_message(data_bag_item2, {BagName, ItemName}) ->
    error_message_envelope(iolist_to_binary(["Cannot load data bag item ", ItemName,
                                             " for data bag ", BagName]));
not_found_message(data_bag_missing_for_item_post, BagName) ->
    error_message_envelope(iolist_to_binary(["No data bag '",
                                             BagName,
                                             "' could be found. Please create this ",
                                             "data bag before adding items to it."]));
not_found_message(sandbox, SandboxId) ->
    error_message_envelope(iolist_to_binary([<<"No such sandbox '">>, SandboxId,
                                             <<"'.">>]));
not_found_message(environment, Name) ->
    error_message_envelope(iolist_to_binary(["Cannot load environment ", Name]));
not_found_message(organization, Name) ->
    error_message_envelope(iolist_to_binary(["Cannot load organization ", Name]));
not_found_message(cookbook, Name) when is_binary(Name) ->
    error_message_envelope(iolist_to_binary(["Cannot find a cookbook named ", Name]));<commit_msg>Fix case clause for Groups not found<commit_after>%% -*- erlang-indent-level: 4;indent-tabs-mode: nil; fill-column: 92 -*-
%% ex: ts=4 sw=4 et
%% @author <NAME>
%% @author <NAME> <<EMAIL>>
%% @author <NAME> <<EMAIL>>
%% @author <NAME>
%% Copyright 2011-2015 Chef Software, Inc. All Rights Reserved.
%%
%% This file is provided to you 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(chef_wm_util).

-export([base_uri/1,
         environment_not_found_message/1,
         error_message_envelope/1,
         extract_from_path/2,
         get_header_fun/2,
         fetch_org_metadata/1,
         base_mods/0,
         not_found_message/2,
         num_versions/1,
         num_versions/2,
         object_name/2,
         set_json_body/2,
         set_uri_of_created_resource/2,
         set_location_of_created_resource/2,
         with_error_body/2
        ]).

-include("oc_chef_wm.hrl").

%% TODO: These types are just placeholders until we get all the types cleaned up
-type ejson() :: {[tuple()]}.

%% @doc Report the names of the "pluggable" modules determined at compile time. Useful for
%% debugging builds.
base_mods() ->
    [{base_resource, oc_chef_wm}, {base_routes, oc_chef_wm_routes}].

%% @doc Returns the base URI for the server as called by the client as a string.
%% By default, use the Host: header value, or if configured with the atom 'host_header'
%% Otherwise, use the configured url.
-spec base_uri(wm_req()) -> string().
base_uri(Req) ->
    case application:get_env(oc_chef_wm, base_resource_url) of
        undefined         -> scheme(Req) ++ "://" ++ vhost(Req);
        {ok, host_header} -> scheme(Req) ++ "://" ++ vhost(Req);
        {ok, ExternalUrl} -> ExternalUrl
    end.

%% @doc Returns the host from the request headers, or use the webmachine default
vhost(Req) ->
    case wrq:get_req_header("host", Req) of
        B when is_binary(B) -> binary_to_list(B);
        S when is_list(S) -> S;
        undefined -> fqdn_with_port(Req)
    end.

%% @doc Returns the host via webmachine
fqdn_with_port(Req) ->
    string:join(wrq:host_tokens(Req), ".") ++ port_string(wrq:port(Req)).

get_header_fun(Req, State = #base_state{header_fun = undefined}) ->
    GetHeader = fun(H) ->
                        Name = case is_binary(H) of
                                   true -> binary_to_list(H);
                                   false -> H
                               end,
                        case wrq:get_req_header(string:to_lower(Name), Req) of
                            B when is_binary(B) -> B;
                            "" -> undefined; %% We want to treat empty header values as missing
                            % Possible DOS attack if this header includes a massive string and possibility
                            % of exhausting the heap?
                            S when is_list(S) -> iolist_to_binary(S);
                            undefined -> undefined
                        end
                end,
    {GetHeader, State#base_state{header_fun = GetHeader}};
get_header_fun(_Req, State) ->
    {State#base_state.header_fun, State}.

fetch_org_metadata(#base_state{organization_name = undefined}) ->
    {undefined, undefined};
fetch_org_metadata(#base_state{organization_guid = Id, organization_authz_id=AuthzId}) when is_binary(Id) ->
    {Id, AuthzId};
fetch_org_metadata(#base_state{organization_guid = undefined,
                               organization_name = OrgName,
                               chef_db_context = DbContext}) ->
    case chef_db:fetch_org_metadata(DbContext, OrgName) of
        not_found -> throw({org_not_found, OrgName});
        {Guid, AuthzId} -> {Guid, AuthzId}
    end.

-spec environment_not_found_message( bin_or_string() ) -> ejson().
environment_not_found_message(EnvName) ->
    error_message_envelope(iolist_to_binary([<<"Cannot load environment '">>, EnvName, <<"'">>])).

%% TODO: Why are these messages phrased differently?  This is what the Ruby endpoints were
%% doing, FYI.  Is this something we're stuck with, or can we update the API and normalize
%% these messages?
-spec not_found_message( node | role | group | data_bag | data_bag_item1 |
                         data_bag_item2 | client | data_bag_missing_for_item_post |
                         environment | sandbox | sandboxes | cookbook |
                         cookbook_version | user | invitation | policy | policy_group,
                         bin_or_string() | {bin_or_string(), bin_or_string()} ) -> ejson().
not_found_message(node, Name) ->
    error_message_envelope(iolist_to_binary(["node '", Name, "' not found"]));
not_found_message(role, Name) ->
    error_message_envelope(iolist_to_binary(["Cannot load role ", Name]));
not_found_message(group, Name) ->
    error_message_envelope(iolist_to_binary(["group '", Name, "' not found"]));
not_found_message(data_bag, Name) ->
    error_message_envelope(iolist_to_binary(["Cannot load data bag ", Name]));
not_found_message(data_bag_item1, {BagName, ItemName}) ->
    error_message_envelope(iolist_to_binary(["Cannot load data bag ", BagName, " item ",
                                             ItemName]));
not_found_message(data_bag_item2, {BagName, ItemName}) ->
    error_message_envelope(iolist_to_binary(["Cannot load data bag item ", ItemName,
                                             " for data bag ", BagName]));
not_found_message(data_bag_missing_for_item_post, BagName) ->
    error_message_envelope(iolist_to_binary(["No data bag '",
                                             BagName,
                                             "' could be found. Please create this ",
                                             "data bag before adding items to it."]));
not_found_message(sandbox, SandboxId) ->
    error_message_envelope(iolist_to_binary([<<"No such sandbox '">>, SandboxId,
                                             <<"'.">>]));
not_found_message(environment, Name) ->
    error_message_envelope(iolist_to_binary(["Cannot load environment ", Name]));
not_found_message(organization, Name) ->
    error_message_envelope(iolist_to_binary(["Cannot load organization ", Name]));
not_found_message(cookbook, Name) when is_binary(Name) ->
    error_message_envelope(iolist_to_binary(["Cannot find a cookbook named ", Name]));
{'fraction_non_alphanumeric': 0.1123169553259496, 'fraction_numerical': 0.005351966104214673, 'mean_word_length': 2.5982883123829903, 'pattern_counts': {'":': 4, '<': 35, '<?xml version=': 0, '>': 112, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 28, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7385672', 'n_tokens_mistral': 3976, 'n_tokens_neox': 3873, 'n_words': 1018}