This commit is contained in:
Родитель
0a18835ea5
Коммит
ea06737655
|
@ -0,0 +1,18 @@
|
|||
language: erlang
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
otp_release:
|
||||
- 21.3
|
||||
- 22.0
|
||||
install:
|
||||
- make
|
||||
- make test-compile
|
||||
- travis_retry pip install --user codecov
|
||||
script:
|
||||
- make test
|
||||
- make dialyzer
|
||||
after_success:
|
||||
- make coverage-report
|
||||
- make codecov
|
||||
- codecov
|
|
@ -0,0 +1,34 @@
|
|||
.PHONY: rel deps test
|
||||
|
||||
all: deps compile
|
||||
|
||||
compile: rebar3
|
||||
./rebar3 compile
|
||||
|
||||
deps: rebar3
|
||||
./rebar3 get-deps
|
||||
|
||||
clean: rebar3
|
||||
./rebar3 clean
|
||||
|
||||
test-deps: rebar3
|
||||
./rebar3 get-deps
|
||||
|
||||
test-compile: rebar3 test-deps
|
||||
./rebar3 compile
|
||||
|
||||
test: test-compile
|
||||
./rebar3 eunit
|
||||
|
||||
coverage-report: _build/test/cover/eunit.coverdata
|
||||
./rebar3 as test coveralls send
|
||||
|
||||
codecov: _build/test/cover/eunit.coverdata
|
||||
./rebar3 as test codecov analyze
|
||||
|
||||
rebar3:
|
||||
wget https://github.com/erlang/rebar3/releases/download/3.13.1/rebar3 &&\
|
||||
chmod u+x rebar3
|
||||
|
||||
dialyzer: rebar3
|
||||
./rebar3 dialyzer
|
|
@ -0,0 +1,9 @@
|
|||
mongoose_jid
|
||||
=====
|
||||
|
||||
An OTP library
|
||||
|
||||
Build
|
||||
-----
|
||||
|
||||
$ rebar3 compile
|
|
@ -0,0 +1,149 @@
|
|||
#include "erl_nif.h"
|
||||
#include <cstring>
|
||||
|
||||
ERL_NIF_TERM
|
||||
mk_error(ErlNifEnv* env)
|
||||
{
|
||||
ERL_NIF_TERM ret;
|
||||
enif_make_existing_atom(env, "error", &ret, ERL_NIF_LATIN1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ERL_NIF_TERM
|
||||
from_binary_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||
{
|
||||
if(argc != 1) {
|
||||
return enif_make_badarg(env);
|
||||
}
|
||||
|
||||
ErlNifBinary bin;
|
||||
if (!enif_inspect_binary(env, argv[0], &bin)) {
|
||||
return enif_make_badarg(env);
|
||||
}
|
||||
|
||||
const unsigned size = bin.size;
|
||||
unsigned commercial_at = -1;
|
||||
unsigned slash = size;
|
||||
for (unsigned i = 0; i < size ; ++i) {
|
||||
switch(bin.data[i]) {
|
||||
case '/':
|
||||
if (slash == size) {
|
||||
slash = i;
|
||||
goto end_loop;
|
||||
// If we found a slash first, we don't care about commercial_ats anymore
|
||||
// https://tools.ietf.org/html/rfc7622#section-3.2
|
||||
}
|
||||
break;
|
||||
case '@':
|
||||
if (commercial_at == -1) {
|
||||
commercial_at = i;
|
||||
} else
|
||||
return mk_error(env);
|
||||
break;
|
||||
}
|
||||
}
|
||||
end_loop:
|
||||
if (commercial_at == 0 || slash == 0) {
|
||||
return mk_error(env);
|
||||
}
|
||||
|
||||
unsigned host_size = slash - commercial_at - 1;
|
||||
if (host_size == 0) return mk_error(env);
|
||||
ERL_NIF_TERM host;
|
||||
unsigned char *host_data = enif_make_new_binary(env, host_size, &host);
|
||||
std::memcpy(host_data, &(bin.data[commercial_at + 1]), host_size);
|
||||
|
||||
ERL_NIF_TERM resource;
|
||||
unsigned res_size = slash >= size - 1 ? 0 : size - slash - 1;
|
||||
unsigned char *res_data = enif_make_new_binary(env, res_size, &resource);
|
||||
std::memcpy(res_data, &(bin.data[slash + 1]), res_size);
|
||||
|
||||
ERL_NIF_TERM user;
|
||||
unsigned user_size = commercial_at == -1 ? 0 : commercial_at;
|
||||
unsigned char *user_data = enif_make_new_binary(env, user_size, &user);
|
||||
std::memcpy(user_data, &(bin.data[0]), user_size);
|
||||
|
||||
return enif_make_tuple3(
|
||||
env,
|
||||
user,
|
||||
host,
|
||||
resource);
|
||||
}
|
||||
|
||||
|
||||
static ERL_NIF_TERM
|
||||
to_binary(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||
{
|
||||
if(argc != 1)
|
||||
return enif_make_badarg(env);
|
||||
if (enif_is_binary(env, argv[0]))
|
||||
return argv[0];
|
||||
|
||||
int arity;
|
||||
const ERL_NIF_TERM *tuple;
|
||||
if (!enif_get_tuple(env, argv[0], &arity, &tuple))
|
||||
return enif_make_badarg(env);
|
||||
|
||||
ErlNifBinary user = {};
|
||||
ErlNifBinary server = {};
|
||||
ErlNifBinary resource = {};
|
||||
switch(arity) {
|
||||
case 7: // It is a #jid record, so from
|
||||
// {jid, User, Server, Resource, LUser, LServer, LResource}
|
||||
// extract fields 1, 2, and 3 (U, S, and R, it's 0-indexed)
|
||||
if (!enif_inspect_binary(env, tuple[1], &user)
|
||||
|| !enif_inspect_binary(env, tuple[2], &server)
|
||||
|| !enif_inspect_binary(env, tuple[3], &resource))
|
||||
return enif_make_badarg(env);
|
||||
break;
|
||||
case 2: // {User, Server}
|
||||
if (!enif_inspect_binary(env, tuple[0], &user)
|
||||
|| !enif_inspect_binary(env, tuple[1], &server))
|
||||
return enif_make_badarg(env);
|
||||
break;
|
||||
case 3: // {User, Server, Resource}
|
||||
if (!enif_inspect_binary(env, tuple[0], &user)
|
||||
|| !enif_inspect_binary(env, tuple[1], &server)
|
||||
|| !enif_inspect_binary(env, tuple[2], &resource))
|
||||
return enif_make_badarg(env);
|
||||
break;
|
||||
default:
|
||||
return enif_make_badarg(env);
|
||||
}
|
||||
// Process those three binaries
|
||||
unsigned index = 0;
|
||||
unsigned len =
|
||||
server.size + // there's always a server
|
||||
(user.size == 0 ? 0 : user.size + 1) + // user plus commercial_at, if any
|
||||
(resource.size == 0 ? 0 : resource.size + 1); // res plus slash, if any
|
||||
|
||||
ERL_NIF_TERM final_jid;
|
||||
unsigned char *final_jid_data = enif_make_new_binary(env, len, &final_jid);
|
||||
std::memset(final_jid_data, 0, len);
|
||||
// copy the user field and the commercial_at if there's a user
|
||||
if (user.size != 0) {
|
||||
std::memcpy(final_jid_data, user.data, user.size);
|
||||
std::memcpy(final_jid_data + user.size, "@", 1);
|
||||
index += user.size + 1;
|
||||
}
|
||||
// always copy the server field
|
||||
std::memcpy(final_jid_data + index, server.data, server.size);
|
||||
index += server.size;
|
||||
// copy the slash and the resource field if there's a resource
|
||||
if (resource.size != 0) {
|
||||
std::memcpy(final_jid_data + index, "/", 1);
|
||||
std::memcpy(final_jid_data + index + 1, resource.data, resource.size);
|
||||
}
|
||||
return final_jid;
|
||||
}
|
||||
|
||||
static ErlNifFunc jid_nif_funcs[] = {
|
||||
{"to_binary", 1, to_binary},
|
||||
{"from_binary_nif", 1, from_binary_nif}
|
||||
};
|
||||
|
||||
static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info) {
|
||||
return 0;
|
||||
};
|
||||
|
||||
ERL_NIF_INIT(jid, jid_nif_funcs, NULL, NULL, upgrade, NULL);
|
|
@ -0,0 +1,8 @@
|
|||
-record(jid, {user = <<>> :: jid:user(),
|
||||
server = <<>> :: jid:server(),
|
||||
resource = <<>> :: jid:resource(),
|
||||
luser = <<>> :: jid:luser(),
|
||||
lserver = <<>> :: jid:lserver(),
|
||||
lresource = <<>> :: jid:lresource()
|
||||
}).
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
{erl_opts, [debug_info]}.
|
||||
|
||||
{deps,
|
||||
[
|
||||
{stringprep, {git, "https://github.com/processone/stringprep.git", {tag, "1.0.19"}}}
|
||||
]}.
|
||||
|
||||
{plugins, [pc]}.
|
||||
|
||||
{artifacts, ["priv/jid.so"]}.
|
||||
|
||||
{port_specs,
|
||||
[
|
||||
{
|
||||
% Any arch
|
||||
".*",
|
||||
% Create library
|
||||
"priv/jid.so",
|
||||
% From files
|
||||
["c_src/*.cpp"],
|
||||
% Using options
|
||||
[ {env, [{ "CXXFLAGS", "$CXXFLAGS -O3 -std=c++11" }]} ]
|
||||
}
|
||||
]}.
|
||||
|
||||
{provider_hooks,
|
||||
[
|
||||
{post,
|
||||
[
|
||||
{compile, {pc, compile}},
|
||||
{clean, {pc, clean}}
|
||||
]}
|
||||
]}.
|
||||
|
||||
{profiles,
|
||||
[
|
||||
{test,
|
||||
[
|
||||
{deps, [{proper, "1.3.0"}]},
|
||||
{plugins,
|
||||
[
|
||||
pc,
|
||||
coveralls,
|
||||
{rebar3_codecov,
|
||||
{git,
|
||||
"https://github.com/zofpolkowska/rebar3_codecov.git",
|
||||
{ref, "3d0af19"}}}
|
||||
]}
|
||||
]}
|
||||
]}.
|
||||
|
||||
{cover_enabled, true}.
|
||||
{cover_export_enabled, true}.
|
|
@ -0,0 +1,10 @@
|
|||
{"1.1.0",
|
||||
[{<<"p1_utils">>,{pkg,<<"p1_utils">>,<<"1.0.18">>},1},
|
||||
{<<"stringprep">>,
|
||||
{git,"https://github.com/processone/stringprep.git",
|
||||
{ref,"0f0df1b10aac78cbe6088b142d172de43fc387a3"}},
|
||||
0}]}.
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"p1_utils">>, <<"3FE224DE5B2E190D730A3C5DA9D6E8540C96484CF4B4692921D1E28F0C32B01C">>}]}
|
||||
].
|
|
@ -0,0 +1,15 @@
|
|||
{application, jid,
|
||||
[{description, "An OTP library"},
|
||||
{vsn, "1.0.0"},
|
||||
{registered, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
stdlib,
|
||||
stringprep
|
||||
]},
|
||||
{env,[]},
|
||||
{modules, []},
|
||||
|
||||
{licenses, ["Apache 2.0"]},
|
||||
{links, []}
|
||||
]}.
|
|
@ -0,0 +1,243 @@
|
|||
%%==============================================================================
|
||||
%% Copyright 2015 Erlang Solutions Ltd.
|
||||
%%
|
||||
%% 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(jid).
|
||||
-on_load(load/0).
|
||||
|
||||
-export([make/3]).
|
||||
-export([make/1]).
|
||||
-export([make_noprep/3]).
|
||||
-export([make_noprep/1]).
|
||||
-export([are_equal/2]).
|
||||
-export([are_bare_equal/2]).
|
||||
-export([from_binary/1]).
|
||||
-export([to_binary/1]).
|
||||
-export([is_nodename/1]).
|
||||
-export([nodeprep/1]).
|
||||
-export([nameprep/1]).
|
||||
-export([resourceprep/1]).
|
||||
-export([to_lower/1]).
|
||||
-export([to_lus/1]).
|
||||
-export([to_bare/1]).
|
||||
-export([replace_resource/2]).
|
||||
-export([binary_to_bare/1]).
|
||||
|
||||
-include("jid.hrl").
|
||||
|
||||
-type user() :: binary().
|
||||
-type server() :: binary().
|
||||
-type resource() :: binary().
|
||||
-type luser() :: binary().
|
||||
-type lserver() :: binary().
|
||||
-type lresource() :: binary().
|
||||
|
||||
-type jid() :: #jid{}.
|
||||
-type ljid() :: {luser(), lserver(), lresource()}.
|
||||
|
||||
%% A tuple-style JID
|
||||
-type simple_jid() :: {user(), server(), resource()}.
|
||||
|
||||
-type simple_bare_jid() :: {LUser :: luser(), LServer :: lserver()}.
|
||||
|
||||
%% A tuple-style JID without resource part
|
||||
-type literal_jid() :: binary().
|
||||
|
||||
-export_type([jid/0,
|
||||
ljid/0,
|
||||
simple_bare_jid/0,
|
||||
simple_jid/0,
|
||||
literal_jid/0,
|
||||
user/0, server/0, resource/0,
|
||||
luser/0, lserver/0, lresource/0
|
||||
]).
|
||||
|
||||
-define(SANE_LIMIT, 1024).
|
||||
-define(XMPP_JID_SIZE_LIMIT, 3071).
|
||||
% Maximum JID size in octets (bytes) as defined in
|
||||
% https://tools.ietf.org/html/rfc7622#section-3.1
|
||||
|
||||
-spec make(User :: user(), Server :: server(), Res :: resource()) -> jid() | error.
|
||||
make(User, Server, Res) ->
|
||||
case {nodeprep(User), nameprep(Server), resourceprep(Res)} of
|
||||
{error, _, _} -> error;
|
||||
{_, error, _} -> error;
|
||||
{_, _, error} -> error;
|
||||
{LUser, LServer, LRes} ->
|
||||
#jid{user = User,
|
||||
server = Server,
|
||||
resource = Res,
|
||||
luser = LUser,
|
||||
lserver = LServer,
|
||||
lresource = LRes}
|
||||
end.
|
||||
|
||||
-spec make(simple_jid()) -> jid() | error.
|
||||
make({User, Server, Resource}) ->
|
||||
make(User, Server, Resource).
|
||||
|
||||
-spec make_noprep(User :: luser(),
|
||||
Server :: lserver(),
|
||||
Resource :: lresource()) -> jid().
|
||||
make_noprep(LUser, LServer, LResource) ->
|
||||
#jid{user = LUser,
|
||||
server = LServer,
|
||||
resource = LResource,
|
||||
luser = LUser,
|
||||
lserver = LServer,
|
||||
lresource = LResource}.
|
||||
|
||||
-spec make_noprep(simple_jid()) -> jid().
|
||||
make_noprep({LUser, LServer, LResource}) ->
|
||||
make_noprep(LUser, LServer, LResource).
|
||||
|
||||
-spec are_equal(jid(), jid()) -> boolean().
|
||||
are_equal(#jid{luser = LUser, lserver = LServer, lresource = LRes},
|
||||
#jid{luser = LUser, lserver = LServer, lresource = LRes}) ->
|
||||
true;
|
||||
are_equal(_, _) ->
|
||||
false.
|
||||
|
||||
%% @doc Returns true if `are_equal(to_bare(A), to_bare(B))'
|
||||
-spec are_bare_equal(jid() | ljid(), jid() | ljid()) -> boolean().
|
||||
are_bare_equal(#jid{luser = LUser, lserver = LServer},
|
||||
#jid{luser = LUser, lserver = LServer}) ->
|
||||
true;
|
||||
are_bare_equal(#jid{luser = LUser, lserver = LServer}, {LUser, LServer, _}) ->
|
||||
true;
|
||||
are_bare_equal({LUser, LServer, _}, #jid{luser = LUser, lserver = LServer}) ->
|
||||
true;
|
||||
are_bare_equal({LUser, LServer, _}, {LUser, LServer, _}) ->
|
||||
true;
|
||||
are_bare_equal(_, _) ->
|
||||
false.
|
||||
|
||||
-spec from_binary(binary()) -> jid() | error.
|
||||
from_binary(J) when is_binary(J), byte_size(J) < ?XMPP_JID_SIZE_LIMIT ->
|
||||
case from_binary_nif(J) of
|
||||
{U, H, R} -> make(U, H, R);
|
||||
error -> error
|
||||
end;
|
||||
from_binary(_) ->
|
||||
error.
|
||||
|
||||
%% Original Erlang equivalent can be found in test/jid_SUITE.erl,
|
||||
%% together with `proper` generators to check for equivalence
|
||||
-spec from_binary_nif(binary()) -> simple_jid() | error.
|
||||
from_binary_nif(_) ->
|
||||
erlang:nif_error(not_loaded).
|
||||
|
||||
%% Original Erlang equivalent can be found in test/jid_SUITE.erl,
|
||||
%% together with `proper` generators to check for equivalence
|
||||
-spec to_binary(simple_jid() | simple_bare_jid() | jid()) -> binary().
|
||||
to_binary(_) ->
|
||||
erlang:nif_error(not_loaded).
|
||||
|
||||
-spec is_nodename(<<>> | binary()) -> boolean().
|
||||
is_nodename(<<>>) ->
|
||||
false;
|
||||
is_nodename(J) ->
|
||||
nodeprep(J) /= error.
|
||||
|
||||
-spec validate_binary_size(binary()) -> binary() | error.
|
||||
validate_binary_size(R) when size(R) < ?SANE_LIMIT ->
|
||||
R;
|
||||
validate_binary_size(_) ->
|
||||
error.
|
||||
|
||||
-spec nodeprep(user()) -> lserver() | error.
|
||||
nodeprep(S) when is_binary(S), size(S) < ?SANE_LIMIT ->
|
||||
R = stringprep:nodeprep(S),
|
||||
validate_binary_size(R);
|
||||
nodeprep(_) ->
|
||||
error.
|
||||
|
||||
-spec nameprep(server()) -> luser() | error.
|
||||
nameprep(S) when is_binary(S), size(S) < ?SANE_LIMIT ->
|
||||
R = stringprep:nameprep(S),
|
||||
validate_binary_size(R);
|
||||
nameprep(_) ->
|
||||
error.
|
||||
|
||||
-spec resourceprep(resource()) -> lresource() | error.
|
||||
resourceprep(S) when size(S) < ?SANE_LIMIT ->
|
||||
R = stringprep:resourceprep(S),
|
||||
validate_binary_size(R);
|
||||
resourceprep(_) ->
|
||||
error.
|
||||
|
||||
-spec to_lower(simple_jid()) -> simple_jid() | error;
|
||||
(jid()) -> jid().
|
||||
to_lower(#jid{luser = U, lserver = S, lresource = R}) ->
|
||||
{U, S, R};
|
||||
to_lower({U, S, R}) ->
|
||||
case {jid:nodeprep(U), jid:nameprep(S), jid:resourceprep(R)} of
|
||||
{LUser, LServer, LResource} when LUser /= error, LServer /= error, LResource /= error ->
|
||||
{LUser, LServer, LResource};
|
||||
_Error ->
|
||||
error
|
||||
end.
|
||||
|
||||
-spec to_lus(jid() | ljid()) -> simple_bare_jid();
|
||||
(error) -> error.
|
||||
to_lus(#jid{luser = U, lserver = S}) ->
|
||||
{U, S};
|
||||
to_lus({U, S, _}) ->
|
||||
{U, S};
|
||||
to_lus(error) ->
|
||||
error.
|
||||
|
||||
-spec to_bare(simple_jid()) -> simple_jid();
|
||||
(jid()) -> jid();
|
||||
(error) -> error.
|
||||
to_bare(#jid{} = JID) ->
|
||||
JID#jid{resource = <<>>, lresource = <<>>};
|
||||
to_bare({U, S, _R}) ->
|
||||
{U, S, <<>>};
|
||||
to_bare(error) ->
|
||||
error.
|
||||
|
||||
-spec replace_resource(jid(), resource()) -> jid() | error.
|
||||
replace_resource(#jid{} = JID, Resource) ->
|
||||
case resourceprep(Resource) of
|
||||
error -> error;
|
||||
LResource ->
|
||||
JID#jid{resource = Resource, lresource = LResource}
|
||||
end.
|
||||
|
||||
-spec binary_to_bare(binary()) -> jid() | error.
|
||||
binary_to_bare(JID) when is_binary(JID) ->
|
||||
case from_binary(JID) of
|
||||
error ->
|
||||
error;
|
||||
#jid{} = Result ->
|
||||
to_bare(Result)
|
||||
end.
|
||||
|
||||
|
||||
%%%===================================================================
|
||||
%%% Load NIF
|
||||
%%%===================================================================
|
||||
-spec load() -> any().
|
||||
load() ->
|
||||
PrivDir = case code:priv_dir(?MODULE) of
|
||||
{error, _} ->
|
||||
EbinDir = filename:dirname(code:which(?MODULE)),
|
||||
AppPath = filename:dirname(EbinDir),
|
||||
filename:join(AppPath, "priv");
|
||||
Path ->
|
||||
Path
|
||||
end,
|
||||
erlang:load_nif(filename:join(PrivDir, ?MODULE_STRING), none),
|
||||
stringprep:start().
|
|
@ -0,0 +1,350 @@
|
|||
-module(jid_SUITE).
|
||||
|
||||
-include_lib("proper/include/proper.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include("jid.hrl").
|
||||
|
||||
-export([all/0, groups/0]).
|
||||
|
||||
-export([
|
||||
binary_to_jid_succeeds_with_valid_binaries/1,
|
||||
binary_to_jid_fails_with_invalid_binaries/1,
|
||||
binary_to_jid_fails_with_empty_binary/1,
|
||||
make_jid_fails_on_binaries_that_are_too_long/1,
|
||||
make_is_independent_of_the_input_format/1,
|
||||
make_noprep_and_make_have_equal_raw_jid/1,
|
||||
make_noprep_is_independent_of_the_input_format/1,
|
||||
jid_to_lower_fails_if_any_binary_is_invalid/1,
|
||||
jid_replace_resource_failes_for_invalid_resource/1,
|
||||
nodeprep_fails_with_too_long_username/1,
|
||||
nameprep_fails_with_too_long_domain/1,
|
||||
resourceprep_fails_with_too_long_resource/1,
|
||||
from_binary_fails_with_too_long_input/1,
|
||||
nodeprep_fails_with_incorrect_username/1,
|
||||
resourceprep_fails_with_incorrect_resource/1,
|
||||
nameprep_fails_with_incorrect_domain/1,
|
||||
is_nodename_fails_for_empty_binary/1,
|
||||
compare_bare_jids/1,
|
||||
compare_bare_jids_doesnt_depend_on_the_order/1,
|
||||
compare_bare_with_jids_structs_and_bare_jids/1,
|
||||
binary_to_bare_equals_binary_and_then_bare/1,
|
||||
to_lower_to_bare_equals_to_bare_to_lower/1,
|
||||
make_to_lus_equals_to_lower_to_lus/1
|
||||
]).
|
||||
|
||||
-export([
|
||||
with_nif_to_binary/1,
|
||||
with_nif_from_binary/1
|
||||
]).
|
||||
|
||||
|
||||
all() -> [
|
||||
{group, common},
|
||||
{group, old_comparison}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{common, [parallel], [
|
||||
binary_to_jid_succeeds_with_valid_binaries,
|
||||
binary_to_jid_fails_with_invalid_binaries,
|
||||
binary_to_jid_fails_with_empty_binary,
|
||||
make_jid_fails_on_binaries_that_are_too_long,
|
||||
make_is_independent_of_the_input_format,
|
||||
make_noprep_and_make_have_equal_raw_jid,
|
||||
make_noprep_is_independent_of_the_input_format,
|
||||
jid_to_lower_fails_if_any_binary_is_invalid,
|
||||
jid_replace_resource_failes_for_invalid_resource,
|
||||
nodeprep_fails_with_too_long_username,
|
||||
nameprep_fails_with_too_long_domain,
|
||||
resourceprep_fails_with_too_long_resource,
|
||||
from_binary_fails_with_too_long_input,
|
||||
nodeprep_fails_with_incorrect_username,
|
||||
resourceprep_fails_with_incorrect_resource,
|
||||
nameprep_fails_with_incorrect_domain,
|
||||
is_nodename_fails_for_empty_binary,
|
||||
compare_bare_jids,
|
||||
compare_bare_jids_doesnt_depend_on_the_order,
|
||||
compare_bare_with_jids_structs_and_bare_jids,
|
||||
binary_to_bare_equals_binary_and_then_bare,
|
||||
to_lower_to_bare_equals_to_bare_to_lower,
|
||||
make_to_lus_equals_to_lower_to_lus
|
||||
]},
|
||||
{old_comparison, [parallel], [
|
||||
with_nif_from_binary,
|
||||
with_nif_to_binary
|
||||
]}
|
||||
].
|
||||
|
||||
binary_to_jid_succeeds_with_valid_binaries(_C) ->
|
||||
Prop = ?FORALL(BinJid, (jid_gen:jid()),
|
||||
(is_record(jid:from_binary(BinJid), jid))),
|
||||
run_property(Prop, 200, 1, 100).
|
||||
|
||||
|
||||
binary_to_jid_fails_with_invalid_binaries(_C) ->
|
||||
Prop = ?FORALL(BinJid, jid_gen:invalid_jid(),
|
||||
error == jid:from_binary(BinJid)),
|
||||
run_property(Prop, 200, 1, 100).
|
||||
|
||||
binary_to_jid_fails_with_empty_binary(_) ->
|
||||
error = jid:from_binary(<<>>).
|
||||
|
||||
make_jid_fails_on_binaries_that_are_too_long(_) ->
|
||||
Prop = ?FORALL({U, S, R},
|
||||
{jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
|
||||
case element_length_is_too_big([U,S,R]) of
|
||||
true -> error == jid:make(U,S,R);
|
||||
false -> is_record(jid:make(U,S,R), jid)
|
||||
end),
|
||||
run_property(Prop, 50, 500, 1500).
|
||||
|
||||
make_is_independent_of_the_input_format(_) ->
|
||||
Prop = ?FORALL({U, S, R},
|
||||
{jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
|
||||
jid:make(U,S,R) == jid:make({U,S,R})),
|
||||
run_property(Prop, 100, 1, 500).
|
||||
|
||||
make_noprep_and_make_have_equal_raw_jid(_) ->
|
||||
Prop = ?FORALL({U, S, R},
|
||||
{jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
|
||||
begin
|
||||
#jid{user = U, server = S, resource = R} = jid:make(U, S, R),
|
||||
#jid{user = U, server = S, resource = R} = jid:make_noprep(U, S, R),
|
||||
true
|
||||
end
|
||||
),
|
||||
run_property(Prop, 10, 1, 500).
|
||||
|
||||
|
||||
make_noprep_is_independent_of_the_input_format(_) ->
|
||||
Prop = ?FORALL({U, S, R},
|
||||
{jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
|
||||
jid:make_noprep(U,S,R) == jid:make_noprep({U,S,R})),
|
||||
run_property(Prop, 10, 1, 500).
|
||||
|
||||
element_length_is_too_big(Els) ->
|
||||
lists:any(fun(El) -> size(El) >= 1024 end, Els).
|
||||
|
||||
jid_to_lower_fails_if_any_binary_is_invalid(_) ->
|
||||
Prop = ?FORALL({U, S, R},
|
||||
{jid_gen:maybe_valid_username(), jid_gen:maybe_valid_domain(), jid_gen:maybe_valid_resource()},
|
||||
case jid:to_lower({U, S, R}) of
|
||||
{LU, LS, LR} ->
|
||||
jid:nodeprep(U) == LU andalso
|
||||
jid:nameprep(S) == LS andalso
|
||||
jid:resourceprep(R) == LR;
|
||||
error ->
|
||||
jid:nodeprep(U) == error orelse
|
||||
jid:nameprep(S) == error orelse
|
||||
jid:resourceprep(R) == error
|
||||
end),
|
||||
|
||||
run_property(Prop, 150, 1, 42).
|
||||
|
||||
nodeprep_fails_with_too_long_username(_C) ->
|
||||
Prop = ?FORALL(Bin, jid_gen:username(),
|
||||
error == jid:nodeprep(Bin)),
|
||||
run_property(Prop, 10, 1024, 2048).
|
||||
|
||||
nameprep_fails_with_too_long_domain(_C) ->
|
||||
Prop = ?FORALL(Bin, jid_gen:domain(),
|
||||
error == jid:nameprep(Bin)),
|
||||
run_property(Prop, 10, 1024, 2048).
|
||||
|
||||
resourceprep_fails_with_too_long_resource(_C) ->
|
||||
Prop = ?FORALL(Bin, jid_gen:resource(),
|
||||
error == jid:resourceprep(Bin)),
|
||||
run_property(Prop, 10, 1024, 2048).
|
||||
|
||||
from_binary_fails_with_too_long_input(_C) ->
|
||||
Prop = ?FORALL(Bin, jid_gen:jid(),
|
||||
error == jid:from_binary(Bin)),
|
||||
run_property(Prop, 5, 3071, 5048).
|
||||
|
||||
jid_replace_resource_failes_for_invalid_resource(_) ->
|
||||
Prop = ?FORALL({BinJid, MaybeCorrectRes},
|
||||
{jid_gen:bare_jid(), jid_gen:maybe_valid_resource()},
|
||||
jid_replace_resource(BinJid, MaybeCorrectRes)),
|
||||
run_property(Prop, 100, 1, 42).
|
||||
|
||||
jid_replace_resource(BinJid, Res) ->
|
||||
Jid = jid:from_binary(BinJid),
|
||||
Jid2 = jid:replace_resource(Jid, Res),
|
||||
check_jid_replace_resource_output(Res, Jid2).
|
||||
|
||||
check_jid_replace_resource_output(Resource, error) ->
|
||||
jid:resourceprep(Resource) == error;
|
||||
check_jid_replace_resource_output(Resource, #jid{}) ->
|
||||
jid:resourceprep(Resource) =/= error.
|
||||
|
||||
|
||||
nodeprep_fails_with_incorrect_username(_) ->
|
||||
Prop = ?FORALL(Bin, jid_gen:invalid_username(),
|
||||
error == jid:nodeprep(Bin)),
|
||||
run_property(Prop, 100, 1, 500).
|
||||
|
||||
resourceprep_fails_with_incorrect_resource(_) ->
|
||||
Prop = ?FORALL(Bin, jid_gen:invalid_resource(),
|
||||
error == jid:resourceprep(Bin)),
|
||||
run_property(Prop, 100, 1, 500).
|
||||
|
||||
nameprep_fails_with_incorrect_domain(_) ->
|
||||
Prop = ?FORALL(Bin, jid_gen:invalid_domain(),
|
||||
error == jid:nameprep(Bin)),
|
||||
run_property(Prop, 100, 1, 500).
|
||||
|
||||
is_nodename_fails_for_empty_binary(_) ->
|
||||
false = jid:is_nodename(<<>>).
|
||||
|
||||
compare_bare_jids_doesnt_depend_on_the_order(_) ->
|
||||
Prop = ?FORALL(Val,
|
||||
oneof([
|
||||
{jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
|
||||
jid_gen:maybe_valid_jid()
|
||||
]),
|
||||
begin
|
||||
{AA, BB} = case Val of
|
||||
A when is_tuple(A) -> {A, A};
|
||||
X when is_binary(X) -> {jid:from_binary(X), jid:from_binary(X)}
|
||||
end,
|
||||
equals(jid:are_bare_equal(BB, AA),
|
||||
jid:are_bare_equal(AA, BB))
|
||||
end),
|
||||
run_property(Prop, 200, 1, 100).
|
||||
|
||||
compare_bare_with_jids_structs_and_bare_jids(_) ->
|
||||
Prop = ?FORALL({U, S, R}, {jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
|
||||
begin
|
||||
AA = {U, S, R},
|
||||
BB = jid:make_noprep(U, S, R),
|
||||
equals(jid:are_bare_equal(BB, AA),
|
||||
jid:are_bare_equal(AA, BB))
|
||||
end),
|
||||
run_property(Prop, 200, 1, 100).
|
||||
|
||||
compare_bare_jids(_) ->
|
||||
Prop = ?FORALL(Val, oneof([
|
||||
{jid_gen:maybe_valid_jid(), jid_gen:maybe_valid_jid()},
|
||||
jid_gen:maybe_valid_jid()
|
||||
]),
|
||||
begin
|
||||
{A, B} = case Val of {X, Y} -> {X, Y}; X -> {X, X} end,
|
||||
AA = jid:from_binary(A),
|
||||
BB = jid:from_binary(B),
|
||||
equals(jid:are_equal(jid:to_bare(AA), jid:to_bare(BB)),
|
||||
jid:are_bare_equal(AA, BB))
|
||||
end),
|
||||
run_property(Prop, 200, 1, 100).
|
||||
|
||||
binary_to_bare_equals_binary_and_then_bare(_) ->
|
||||
Prop = ?FORALL(A, jid_gen:maybe_valid_jid(),
|
||||
equals(jid:to_bare(jid:from_binary(A)), jid:binary_to_bare(A))),
|
||||
run_property(Prop, 200, 1, 100).
|
||||
|
||||
to_lower_to_bare_equals_to_bare_to_lower(_) ->
|
||||
Prop = ?FORALL(JID, oneof([jid_gen:jid_struct(),
|
||||
{jid_gen:maybe_valid_username(),
|
||||
jid_gen:maybe_valid_domain(),
|
||||
jid_gen:resource()}]),
|
||||
equals(jid:to_bare(jid:to_lower(JID)),
|
||||
jid:to_lower(jid:to_bare(JID)))),
|
||||
run_property(Prop, 200, 1, 100).
|
||||
|
||||
make_to_lus_equals_to_lower_to_lus(_) ->
|
||||
Prop = ?FORALL({U, S, R}, {jid_gen:maybe_valid_username(),
|
||||
jid_gen:maybe_valid_domain(),
|
||||
jid_gen:maybe_valid_resource()},
|
||||
equals(jid:to_lus(jid:make(U, S, R)),
|
||||
jid:to_lus(jid:to_lower({U, S, R})))),
|
||||
run_property(Prop, 200, 1, 100).
|
||||
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% Original code kept for documentation purposes
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
with_nif_to_binary(_C) ->
|
||||
Prop = ?FORALL(L, jid_gen:jid(), from_binary(L) =:= jid:from_binary(L)),
|
||||
run_property(Prop, 100, 1, 42).
|
||||
|
||||
with_nif_from_binary(_C) ->
|
||||
Prop = ?FORALL(L, jid_gen:from_jid(), to_binary(L) =:= jid:to_binary(L)),
|
||||
run_property(Prop, 100, 1, 42).
|
||||
|
||||
% Some property based testing to check for equivalence
|
||||
% Original code
|
||||
-spec from_binary(binary()) -> error | jid:jid().
|
||||
from_binary(J) ->
|
||||
binary_to_jid1(J, []).
|
||||
|
||||
-spec binary_to_jid1(binary(), [byte()]) -> 'error' | jid:jid().
|
||||
binary_to_jid1(<<$@, _J/binary>>, []) ->
|
||||
error;
|
||||
binary_to_jid1(<<$@, J/binary>>, N) ->
|
||||
binary_to_jid2(J, lists:reverse(N), []);
|
||||
binary_to_jid1(<<$/, _J/binary>>, []) ->
|
||||
error;
|
||||
binary_to_jid1(<<$/, J/binary>>, N) ->
|
||||
binary_to_jid3(J, [], lists:reverse(N), []);
|
||||
binary_to_jid1(<<C, J/binary>>, N) ->
|
||||
binary_to_jid1(J, [C | N]);
|
||||
binary_to_jid1(<<>>, []) ->
|
||||
error;
|
||||
binary_to_jid1(<<>>, N) ->
|
||||
jid:make(<<>>, list_to_binary(lists:reverse(N)), <<>>).
|
||||
|
||||
%% @doc Only one "@" is admitted per JID
|
||||
-spec binary_to_jid2(binary(), [byte()], [byte()]) -> 'error' | jid:jid().
|
||||
binary_to_jid2(<<$@, _J/binary>>, _N, _S) ->
|
||||
error;
|
||||
binary_to_jid2(<<$/, _J/binary>>, _N, []) ->
|
||||
error;
|
||||
binary_to_jid2(<<$/, J/binary>>, N, S) ->
|
||||
binary_to_jid3(J, N, lists:reverse(S), []);
|
||||
binary_to_jid2(<<C, J/binary>>, N, S) ->
|
||||
binary_to_jid2(J, N, [C | S]);
|
||||
binary_to_jid2(<<>>, _N, []) ->
|
||||
error;
|
||||
binary_to_jid2(<<>>, N, S) ->
|
||||
jid:make(list_to_binary(N), list_to_binary(lists:reverse(S)), <<>>).
|
||||
|
||||
-spec binary_to_jid3(binary(), [byte()], [byte()], [byte()]) -> 'error' | jid:jid().
|
||||
binary_to_jid3(<<C, J/binary>>, N, S, R) ->
|
||||
binary_to_jid3(J, N, S, [C | R]);
|
||||
binary_to_jid3(<<>>, N, S, R) ->
|
||||
jid:make(list_to_binary(N), list_to_binary(S), list_to_binary(lists:reverse(R))).
|
||||
|
||||
-spec to_binary(jid:simple_jid() | jid:simple_bare_jid() | jid:jid()) -> binary().
|
||||
to_binary(Jid) when is_binary(Jid) ->
|
||||
% sometimes it is used to format error messages
|
||||
Jid;
|
||||
to_binary(#jid{user = User, server = Server, resource = Resource}) ->
|
||||
to_binary({User, Server, Resource});
|
||||
to_binary({User, Server}) ->
|
||||
to_binary({User, Server, <<>>});
|
||||
to_binary({Node, Server, Resource}) ->
|
||||
S1 = case Node of
|
||||
<<>> ->
|
||||
<<>>;
|
||||
_ ->
|
||||
<<Node/binary, "@">>
|
||||
end,
|
||||
S2 = <<S1/binary, Server/binary>>,
|
||||
S3 = case Resource of
|
||||
<<>> ->
|
||||
S2;
|
||||
_ ->
|
||||
<<S2/binary, "/", Resource/binary>>
|
||||
end,
|
||||
S3.
|
||||
|
||||
|
||||
%% HELPERS
|
||||
run_property(Prop, NumTest, StartSize, StopSize) ->
|
||||
?assert(proper:quickcheck(Prop, [verbose, long_result,
|
||||
{numtests, NumTest},
|
||||
{start_size, StartSize},
|
||||
{max_size, StopSize}])).
|
|
@ -0,0 +1,116 @@
|
|||
-module(jid_gen).
|
||||
|
||||
-export([jid_struct/0]).
|
||||
-export([from_jid/0]).
|
||||
-export([jid/0]).
|
||||
-export([bare_jid/0]).
|
||||
-export([full_jid/0]).
|
||||
-export([username/0]).
|
||||
-export([domain/0]).
|
||||
-export([resource/0]).
|
||||
-export([maybe_valid_jid/0]).
|
||||
-export([invalid_jid/0]).
|
||||
-export([invalid_bare_jid/0]).
|
||||
-export([invalid_full_jid/0]).
|
||||
-export([maybe_valid_username/0]).
|
||||
-export([invalid_username/0]).
|
||||
-export([maybe_valid_domain/0]).
|
||||
-export([invalid_domain/0]).
|
||||
-export([maybe_valid_resource/0]).
|
||||
-export([invalid_resource/0]).
|
||||
|
||||
-include_lib("proper/include/proper.hrl").
|
||||
|
||||
jid_struct() ->
|
||||
?LET({U, S, R}, {username(), domain(), resource()},
|
||||
{jid, U, S, R, U, S, R}).
|
||||
|
||||
from_jid() ->
|
||||
oneof([
|
||||
{jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
|
||||
{<<>>, jid_gen:domain(), jid_gen:resource()},
|
||||
{jid_gen:username(), jid_gen:domain()},
|
||||
{jid, jid_gen:username(), jid_gen:domain(), jid_gen:resource(),
|
||||
jid_gen:username(), jid_gen:domain(), jid_gen:resource()}
|
||||
]).
|
||||
|
||||
jid() ->
|
||||
oneof([full_jid(), bare_jid(), domain()]).
|
||||
|
||||
bare_jid() ->
|
||||
?LET({Username, Domain}, {username(), domain()},
|
||||
<<Username/binary, $@, Domain/binary>>).
|
||||
|
||||
full_jid() ->
|
||||
?LET({BareJid, Resource}, {bare_jid(), resource()},
|
||||
<<BareJid/binary, $/, Resource/binary>>).
|
||||
|
||||
username() ->
|
||||
?SIZED(S, always_correct_xmpp_binary(S)).
|
||||
|
||||
domain() ->
|
||||
?SIZED(S, always_correct_xmpp_binary(round(S*1.5))).
|
||||
|
||||
resource() ->
|
||||
?SIZED(S, always_correct_xmpp_binary(round(S*1.7))).
|
||||
|
||||
maybe_valid_jid() ->
|
||||
oneof([jid(), invalid_jid()]).
|
||||
|
||||
invalid_jid() ->
|
||||
oneof([invalid_full_jid(), invalid_bare_jid()]).
|
||||
|
||||
invalid_bare_jid() ->
|
||||
%%Oh yes, jids like domain/resource are allowed in both ejabberd and MongooseIM
|
||||
?LET({U, S}, {?SUCHTHAT(E, invalid_username(), size(E) == 1 orelse binary:matches(E, <<"/">>) == []),
|
||||
maybe_valid_domain()},
|
||||
<<U/binary, $@, S/binary>>).
|
||||
|
||||
invalid_full_jid() ->
|
||||
?LET({BareJid, R}, {invalid_bare_jid(), resource()},
|
||||
<<BareJid/binary, $/, R/binary>>).
|
||||
|
||||
maybe_valid_username() ->
|
||||
oneof([username(), <<>>, invalid_username()]).
|
||||
|
||||
invalid_username() ->
|
||||
invalid_xmpp_binary(prohibited_output_node()).
|
||||
|
||||
maybe_valid_resource() ->
|
||||
oneof([resource(), <<>>, invalid_resource()]).
|
||||
|
||||
invalid_resource() ->
|
||||
invalid_xmpp_binary([<<238,190,187>>]). %<<"\x{EFBB}"/utf8>>
|
||||
|
||||
maybe_valid_domain() ->
|
||||
oneof([domain(), <<>>, invalid_domain()]).
|
||||
|
||||
invalid_domain() ->
|
||||
invalid_resource().
|
||||
|
||||
|
||||
always_correct_xmpp_binary(S) ->
|
||||
?LET(Str, always_correct_xmpp_string(S), list_to_binary(Str)).
|
||||
|
||||
allowed_output() ->
|
||||
oneof([choose($a, $z),
|
||||
choose($A, $Z),
|
||||
oneof([$., $-, $_]),
|
||||
choose($0, $9)]).
|
||||
|
||||
always_correct_xmpp_string(S) ->
|
||||
[allowed_output() || _ <- lists:seq(1, S)].
|
||||
|
||||
invalid_xmpp_binary(ProhibitedOutput) ->
|
||||
?LET({NotAllowed, Str},
|
||||
{oneof(ProhibitedOutput),
|
||||
frequency([{1, []}, {5, maybe_invalid_xmpp_string(ProhibitedOutput)}])},
|
||||
erlang:iolist_to_binary([NotAllowed | Str])).
|
||||
|
||||
maybe_invalid_xmpp_string(ProhibitedOutput) ->
|
||||
list(
|
||||
oneof([allowed_output(),
|
||||
oneof(ProhibitedOutput)])).
|
||||
|
||||
prohibited_output_node() ->
|
||||
[$", $&, $', $/, $:, $<, $>, $@, " "].
|
Загрузка…
Ссылка в новой задаче