<commit_before>
-module(ddfs_web).

-include("config.hrl").
-include("ddfs.hrl").
-include("ddfs_tag.hrl").

-export([op/3]).

-type json() :: binary() | [binary()] | {'struct', [{binary(), json()}]}.

-spec parse_tag_attribute(string(), atom()) ->
    {nonempty_string(), attrib() | 'all' | 'unknown_attribute'}.
parse_tag_attribute(TagAttrib, DefaultAttrib) ->
    case mochiweb_util:path_split(TagAttrib) of
        {T, ""} -> {T, DefaultAttrib};
        {T, "ddfs:urls"} -> {T, urls};
        {T, "ddfs:read-token"} -> {T, read_token};
        {T, "ddfs:write-token"} -> {T, write_token};
        {T, "ddfs:" ++ _} -> {T, unknown_attribute};
        {T, A} -> {T, {user, list_to_binary(A)}}
    end.

-spec parse_auth_token(module()) -> token().
parse_auth_token(Req) ->
    case Req:get_header_value("authorization") of
        undefined ->
            null;
        "Basic " ++ Auth ->
            case base64:decode(Auth) of
                <<"token:", Token/binary>> ->
                    Token;
                _ ->
                    null
            end;
        _ ->
            % Unknown auth, or basic auth that does not follow the spec.
            null
    end.

-spec op(atom(), string(), module()) -> _.
op('POST', "/ddfs/ctrl/hosted_tags", Req) ->
    Fun =
        fun(BinHost, _Size) ->
            Host = binary_to_list(BinHost),
            case ddfs_master:get_hosted_tags(Host) of
                {ok, Tags} ->
                    okjson(Tags, Req);
                E ->
                    lager:warning("/ddfs/ctrl/hosted_tags failed for ~p", [Host]),
                    on_error(E, Req)
            end
        end,
    process_payload(Fun, Req);

op('GET', "/ddfs/ctrl/gc_stats", Req) ->
    case ddfs_master:gc_stats() of
        {ok, none} ->
            okjson(<<"">>, Req);
        {ok, {{{{TKF, TKB},{TDF, TDB}}, {{BKF, BKB}, {BDF,BDB}}}, TStamp}} ->
            When = disco_util:format_timestamp(TStamp),
            Resp = {struct, [{<<"timestamp">>, When},
                             {<<"stats">>, [{<<"Tags kept">>, [TKF, TKB]},
                                            {<<"Tags deleted">>, [TDF, TDB]},
                                            {<<"Blobs kept">>, [BKF, BKB]},
                                            {<<"Blobs deleted">>, [BDF, BDB]}]}]},
            okjson(Resp, Req);
        E ->
            on_error(E, Req)
    end;

op('GET', "/ddfs/ctrl/gc_status", Req) ->
    case ddfs_gc:gc_request(status) of
        {ok, not_running} ->
            okjson(<<"">>, Req);
        {ok, init_wait} ->
            okjson(<<"GC is waiting for the cluster to stabilize after startup.">>, Req);
        {ok, Phase} ->
            okjson(gc_phase_msg(Phase), Req);
        E ->
            on_error(E, Req)
    end;

op('GET', "/ddfs/ctrl/gc_start", Req) ->
    case ddfs_gc:gc_request(start) of
        {ok, init_wait} ->
            okjson(<<"GC is waiting">>, Req);
        ok ->
            okjson(<<"GC has started">>, Req);
        E ->
            on_error(E, Req)
    end;

op('GET', "/ddfs/ctrl/safe_gc_blacklist", Req) ->
    case ddfs_master:safe_gc_blacklist() of
        {ok, Nodes} ->
            Resp = [list_to_binary(disco:host(N)) || N <- Nodes],
            okjson(Resp, Req);
        E ->
            on_error(E, Req)
    end;

op('GET', "/ddfs/new_blob/" ++ BlobName, Req) ->
    BlobK = list_to_integer(disco:get_setting("DDFS_BLOB_REPLICAS")),
    QS = Req:parse_qs(),
    K = case lists:keyfind("replicas", 1, QS) of
            false -> BlobK;
            {_, X} -> list_to_integer(X)
    end,
    Exc = parse_inclusion(lists:keysearch("exclude", 1, QS)),
    case ddfs:new_blob(ddfs_master, BlobName, K, [], Exc) of
        {ok, Urls} when length(Urls) < K ->
            Req:respond({403, [], ["Not enough nodes for replicas."]});
        too_many_replicas ->
            Req:respond({403, [], ["Not enough nodes for replicas."]});
        invalid_name -><commit_msg>Parse the inclusion list in the erlang code and actually use it.<commit_after>
-module(ddfs_web).

-include("config.hrl").
-include("ddfs.hrl").
-include("ddfs_tag.hrl").

-export([op/3]).

-type json() :: binary() | [binary()] | {'struct', [{binary(), json()}]}.

-spec parse_tag_attribute(string(), atom()) ->
    {nonempty_string(), attrib() | 'all' | 'unknown_attribute'}.
parse_tag_attribute(TagAttrib, DefaultAttrib) ->
    case mochiweb_util:path_split(TagAttrib) of
        {T, ""} -> {T, DefaultAttrib};
        {T, "ddfs:urls"} -> {T, urls};
        {T, "ddfs:read-token"} -> {T, read_token};
        {T, "ddfs:write-token"} -> {T, write_token};
        {T, "ddfs:" ++ _} -> {T, unknown_attribute};
        {T, A} -> {T, {user, list_to_binary(A)}}
    end.

-spec parse_auth_token(module()) -> token().
parse_auth_token(Req) ->
    case Req:get_header_value("authorization") of
        undefined ->
            null;
        "Basic " ++ Auth ->
            case base64:decode(Auth) of
                <<"token:", Token/binary>> ->
                    Token;
                _ ->
                    null
            end;
        _ ->
            % Unknown auth, or basic auth that does not follow the spec.
            null
    end.

-spec op(atom(), string(), module()) -> _.
op('POST', "/ddfs/ctrl/hosted_tags", Req) ->
    Fun =
        fun(BinHost, _Size) ->
            Host = binary_to_list(BinHost),
            case ddfs_master:get_hosted_tags(Host) of
                {ok, Tags} ->
                    okjson(Tags, Req);
                E ->
                    lager:warning("/ddfs/ctrl/hosted_tags failed for ~p", [Host]),
                    on_error(E, Req)
            end
        end,
    process_payload(Fun, Req);

op('GET', "/ddfs/ctrl/gc_stats", Req) ->
    case ddfs_master:gc_stats() of
        {ok, none} ->
            okjson(<<"">>, Req);
        {ok, {{{{TKF, TKB},{TDF, TDB}}, {{BKF, BKB}, {BDF,BDB}}}, TStamp}} ->
            When = disco_util:format_timestamp(TStamp),
            Resp = {struct, [{<<"timestamp">>, When},
                             {<<"stats">>, [{<<"Tags kept">>, [TKF, TKB]},
                                            {<<"Tags deleted">>, [TDF, TDB]},
                                            {<<"Blobs kept">>, [BKF, BKB]},
                                            {<<"Blobs deleted">>, [BDF, BDB]}]}]},
            okjson(Resp, Req);
        E ->
            on_error(E, Req)
    end;

op('GET', "/ddfs/ctrl/gc_status", Req) ->
    case ddfs_gc:gc_request(status) of
        {ok, not_running} ->
            okjson(<<"">>, Req);
        {ok, init_wait} ->
            okjson(<<"GC is waiting for the cluster to stabilize after startup.">>, Req);
        {ok, Phase} ->
            okjson(gc_phase_msg(Phase), Req);
        E ->
            on_error(E, Req)
    end;

op('GET', "/ddfs/ctrl/gc_start", Req) ->
    case ddfs_gc:gc_request(start) of
        {ok, init_wait} ->
            okjson(<<"GC is waiting">>, Req);
        ok ->
            okjson(<<"GC has started">>, Req);
        E ->
            on_error(E, Req)
    end;

op('GET', "/ddfs/ctrl/safe_gc_blacklist", Req) ->
    case ddfs_master:safe_gc_blacklist() of
        {ok, Nodes} ->
            Resp = [list_to_binary(disco:host(N)) || N <- Nodes],
            okjson(Resp, Req);
        E ->
            on_error(E, Req)
    end;

op('GET', "/ddfs/new_blob/" ++ BlobName, Req) ->
    BlobK = list_to_integer(disco:get_setting("DDFS_BLOB_REPLICAS")),
    QS = Req:parse_qs(),
    K = case lists:keyfind("replicas", 1, QS) of
            false -> BlobK;
            {_, X} -> list_to_integer(X)
    end,
    Exc = parse_inclusion(lists:keysearch("exclude", 1, QS)),
    Inc = parse_inclusion(lists:keysearch("include", 1, QS)),
    case ddfs:new_blob(ddfs_master, BlobName, K, Inc, Exc) of
        {ok, Urls} when length(Urls) < K ->
            Req:respond({403, [], ["Not enough nodes for replicas."]});
        too_many_replicas ->
            Req:respond({403, [], ["Not enough nodes for replicas."]});
        invalid_name ->
{'fraction_non_alphanumeric': 0.18013972055888225, 'fraction_numerical': 0.002869261477045908, 'mean_word_length': 1.8469460227272727, 'pattern_counts': {'":': 0, '<': 55, '<?xml version=': 0, '>': 135, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '638269', 'n_tokens_mistral': 2800, 'n_tokens_neox': 2724, 'n_words': 572}