From 7b05c0c2b5769181c0cf7feb8c36e470ff52deea Mon Sep 17 00:00:00 2001 From: "Gavin M. Roy" Date: Fri, 8 Jan 2016 23:21:49 -0500 Subject: [PATCH] WIP commit --- Makefile | 2 +- src/urilib.app.src | 3 +- src/urilib.erl | 132 ++++++++++++++++++++++++++++++++++++++---- test/urilib_tests.erl | 68 ++++++++++++++++++++++ 4 files changed, 192 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 8f21c0e..4db9b37 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ all: get-deps compile build-plt: @dialyzer --build_plt --output_plt ~/.$(PROJECT).plt \ - --apps kernel stdlib erts + --apps kernel stdlib erts inets edoc clean: @( $(REBAR) clean ) diff --git a/src/urilib.app.src b/src/urilib.app.src index 54821cd..ddca254 100644 --- a/src/urilib.app.src +++ b/src/urilib.app.src @@ -1,10 +1,11 @@ {application, urilib, [ {description, "RFC-3986 URI Library"}, {vsn, "0.1.0"}, - {registered, []}, + {registered, [urilib]}, {applications, [ kernel, stdlib, + inets, edoc ]}, {mod, {urilib, []}}, diff --git a/src/urilib.erl b/src/urilib.erl index 51adad7..067e0c3 100644 --- a/src/urilib.erl +++ b/src/urilib.erl @@ -7,7 +7,6 @@ -module(urilib). -export([build/1, - parse/1, parse_uri/1, parse_url/1, encode/1, @@ -30,22 +29,60 @@ %% @doc Returns a URI from the record passed in. %% %% @end -build(_URL) -> - ok. - - --spec parse(string()) -> #uri{}. -parse(URL) -> parse_uri(URL). +build(#uri{scheme=Scheme, userinfo=UserInfo, authority=Authority, + path=Path, query=QArgs, fragment=Fragment}) -> + U1 = url_add_scheme(Scheme), + U2 = url_maybe_add_user(UserInfo, U1), + U3 = url_add_host_and_port(Scheme, + Authority#authority.host, + Authority#authority.port, U2), + U4 = url_add_path(Path, U3), + U5 = url_maybe_add_qargs(QArgs, U4), + url_maybe_add_fragment(Fragment, U5). -spec parse_uri(string()) -> #uri{}. -parse_uri(_URL) -> - ok. +%% @spec parse_uri(URI) -> ParsedURI. +%% where +%% URI = string() +%% ParsedURI = #uri{} +%% @doc Parse a URI string returning the parsed data as a record +%% @end +parse_uri(URI) -> + case http_uri:parse(URI, [{scheme_defaults, http_uri:scheme_defaults()}, {fragment, true}]) of + {ok, {Scheme, UserInfo, Host, Port, Path, Query, Fragment}} -> + #uri{scheme=Scheme, + userinfo=parse_userinfo(UserInfo), + authority=#authority{host=Host, + port=Port}, + path=Path, + query=parse_query(Query), + fragment=Fragment}; + {error, Reason} -> {error, Reason} + end. -spec parse_url(string()) -> #url{}. -parse_url(_URL) -> - ok. +%% @spec parse_url(URL) -> ParsedURL. +%% where +%% URI = string() +%% ParsedURL = #url{} +%% @doc Parse a URL string returning the parsed data as a record +%% @end +parse_url(URL) -> + case http_uri:parse(URL, [{scheme_defaults, http_uri:scheme_defaults()}, {fragment, true}]) of + {ok, {Scheme, UserInfo, Host, Port, Path, Query, Fragment}} -> + User = parse_userinfo(UserInfo), + #url{scheme=Scheme, + username=User#userinfo.username, + password=User#userinfo.password, + host=Host, + port=Port, + path=Path, + query=parse_query(Query), + fragment=Fragment}; + {error, Reason} -> {error, Reason} + end. -spec encode(string()) -> string(). @@ -97,3 +134,76 @@ decode(Value) -> %% @end decode_plus(Value) -> string:join([http_uri:decode(V) || V <- string:tokens(Value, "+")], " "). + + +-spec parse_query(string()) -> []. +%% @private +parse_query([]) -> []; +parse_query(Query) -> + case re:split(Query, "[&|?]", [{return, list}]) of + [""] -> []; + QArgs -> [split_query_arg(Arg) || Arg <- QArgs, Arg =/= []] + end. + + +-spec parse_userinfo(string()) -> #userinfo{}. +%% @private +parse_userinfo([]) -> #userinfo{}; +parse_userinfo(Value) -> + case string:tokens(Value, ":") of + [User, Password] -> #userinfo{username=User, password=Password}; + [User] -> #userinfo{username=User} + end. + + +-spec split_query_arg(string()) -> {string(), string()}. +%% @private +split_query_arg(Argument) -> + [K, V] = string:tokens(Argument, "="), + {K, V}. + + +%% @private +url_add_scheme(Scheme) -> + string:concat(atom_to_list(Scheme), "://"). + + +%% @private +url_maybe_add_user([], URL) -> URL; +url_maybe_add_user(User, URL) -> + string:concat(URL, string:concat(User, "@")). + + +%% @private +url_add_host_and_port(http, Host, 80, URL) -> + string:concat(URL, Host); + + +%% @private +url_add_host_and_port(https, Host, 443, URL) -> + string:concat(URL, Host); +url_add_host_and_port(_, Host, Port, URL) -> + string:concat(URL, string:join([Host, integer_to_list(Port)], ":")). + + +%% @private +url_add_path(Path, URL) -> + Escaped = string:join([edoc_lib:escape_uri(P) || P <- string:tokens(Path, "/")], "/"), + string:join([URL, Escaped], "/"). + + +%% @private +url_maybe_add_qargs([], URL) -> URL; +url_maybe_add_qargs(QArgs, URL) -> + QStr = string:join([string:join([encode_plus(K), encode_plus(V)], "=") || {K,V} <- QArgs], "&"), + string:join([URL, QStr], "?"). + + +%% @private +url_maybe_add_fragment([], URL) -> URL; +url_maybe_add_fragment(Value, URL) -> + Fragment = case string:left(Value, 1) of + "#" -> edoc_lib:escape_uri(string:sub_string(Value, 2)); + _ -> edoc_lib:escape_uri(Value) + end, + string:join([URL, Fragment], "#"). diff --git a/test/urilib_tests.erl b/test/urilib_tests.erl index b992c96..ba2b539 100644 --- a/test/urilib_tests.erl +++ b/test/urilib_tests.erl @@ -2,6 +2,7 @@ -include_lib("eunit/include/eunit.hrl"). +-include("urilib.hrl"). decode_test() -> Value = "foo%2fbar%20baz", @@ -27,3 +28,70 @@ encode_plus1_test() -> Value = "foo/bar baz", Expect = "foo%2fbar+baz", ?assertEqual(Expect, urilib:encode_plus(Value)). + +parse_uri_variation1_test() -> + URI = "amqp://guest:rabbitmq@rabbitmq:5672/%2f?heartbeat=5", + Expect = #uri{scheme=amqp, + userinfo=#userinfo{username="guest", + password="rabbitmq"}, + authority=#authority{host="rabbitmq", port=5672}, + path="/%2f", + query=[{"heartbeat", "5"}], + fragment=[]}, + ?assertEqual(Expect, urilib:parse_uri(URI)). + +parse_uri_variation2_test() -> + URI = "http://www.google.com/search?foo=bar#baz", + Expect = #uri{scheme=http, + userinfo=#userinfo{}, + authority=#authority{host="www.google.com", port=80}, + path="/search", + query=[{"foo", "bar"}], + fragment="#baz"}, + ?assertEqual(Expect, urilib:parse_uri(URI)). + +parse_uri_variation3_test() -> + URI = "https://www.google.com/search", + Expect = #uri{scheme=https, + userinfo=#userinfo{}, + authority=#authority{host="www.google.com", port=443}, + path="/search", + query=[], + fragment=[]}, + ?assertEqual(Expect, urilib:parse_uri(URI)). + +parse_url_variation1_test() -> + URL = "amqp://guest:rabbitmq@rabbitmq:5672/%2f?heartbeat=5", + Expect = #url{scheme=amqp, + username="guest", + password="rabbitmq", + host="rabbitmq", + port=5672, + path="/%2f", + query=[{"heartbeat", "5"}], + fragment=[]}, + ?assertEqual(Expect, urilib:parse_url(URL)). + +parse_url_variation2_test() -> + URL = "http://www.google.com/search?foo=bar#baz", + Expect = #url{scheme=http, + username=undefined, + password=undefined, + host="www.google.com", + port=80, + path="/search", + query=[{"foo", "bar"}], + fragment="#baz"}, + ?assertEqual(Expect, urilib:parse_url(URL)). + +parse_url_variation3_test() -> + URL = "https://www.google.com/search", + Expect = #url{scheme=https, + username=undefined, + password=undefined, + host="www.google.com", + port=443, + path="/search", + query=[], + fragment=[]}, + ?assertEqual(Expect, urilib:parse_url(URL)).