emqx/apps/emqx_ft/src/emqx_ft_schema.erl
Andrew Mayorov 2707b4500f feat(ft-fs): ensure filsystem-safe handling of client input
Also restrict the filenames clients may specify in a filemeta to some
safe subset, as a future proofing measure.
2023-04-07 17:25:22 +03:00

162 lines
4.7 KiB
Erlang

%%--------------------------------------------------------------------
%% Copyright (c) 2021-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% 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(emqx_ft_schema).
-behaviour(hocon_schema).
-include_lib("hocon/include/hoconsc.hrl").
-include_lib("typerefl/include/types.hrl").
-export([namespace/0, roots/0, fields/1, tags/0, desc/1]).
-export([schema/1]).
-type json_value() ::
null
| boolean()
| binary()
| number()
| [json_value()]
| #{binary() => json_value()}.
-reflect_type([json_value/0]).
%%
namespace() -> file_transfer.
tags() ->
[<<"File Transfer">>].
roots() -> [file_transfer].
fields(file_transfer) ->
[
{storage, #{
type => hoconsc:union([
hoconsc:ref(?MODULE, local_storage)
]),
desc => ?DESC("storage")
}}
];
fields(local_storage) ->
[
{type, #{
type => local,
default => local,
required => false,
desc => ?DESC("local_type")
}},
{root, #{
type => binary(),
desc => ?DESC("local_storage_root"),
required => false
}},
{exporter, #{
type => hoconsc:union([
?REF(local_storage_exporter)
]),
desc => ?DESC("local_storage_exporter"),
required => true
}},
{gc, #{
type => ?REF(local_storage_gc),
desc => ?DESC("local_storage_gc"),
required => false
}}
];
fields(local_storage_exporter) ->
[
{type, #{
type => local,
default => local,
required => false,
desc => ?DESC("local_storage_exporter_type")
}},
{root, #{
type => binary(),
desc => ?DESC("local_storage_exporter_root"),
required => false
}}
];
fields(local_storage_gc) ->
[
{interval, #{
type => emqx_schema:duration_ms(),
desc => ?DESC("storage_gc_interval"),
required => false,
default => "1h"
}},
{maximum_segments_ttl, #{
type => emqx_schema:duration_s(),
desc => ?DESC("storage_gc_max_segments_ttl"),
required => false,
default => "24h"
}},
{minimum_segments_ttl, #{
type => emqx_schema:duration_s(),
% desc => ?DESC("storage_gc_min_segments_ttl"),
required => false,
default => "5m",
% NOTE
% This setting does not seem to be useful to an end-user.
hidden => true
}}
].
desc(file_transfer) ->
"File transfer settings";
desc(local_storage) ->
"File transfer local storage settings";
desc(local_storage_exporter) ->
"Exporter settings for the File transfer local storage backend";
desc(local_storage_gc) ->
"Garbage collection settings for the File transfer local storage backend".
schema(filemeta) ->
#{
roots => [
{name,
hoconsc:mk(string(), #{
required => true,
validator => validator(filename)
})},
{size, hoconsc:mk(non_neg_integer())},
{expire_at, hoconsc:mk(non_neg_integer())},
{checksum, hoconsc:mk({atom(), binary()}, #{converter => converter(checksum)})},
{segments_ttl, hoconsc:mk(pos_integer())},
{user_data, hoconsc:mk(json_value())}
]
}.
validator(filename) ->
fun emqx_ft_fs_util:is_filename_safe/1.
converter(checksum) ->
fun
(undefined, #{}) ->
undefined;
({sha256, Bin}, #{make_serializable := true}) ->
_ = is_binary(Bin) orelse throw({expected_type, string}),
_ = byte_size(Bin) =:= 32 orelse throw({expected_length, 32}),
binary:encode_hex(Bin);
(Hex, #{}) ->
_ = is_binary(Hex) orelse throw({expected_type, string}),
_ = byte_size(Hex) =:= 64 orelse throw({expected_length, 64}),
{sha256, binary:decode_hex(Hex)}
end.