diff --git a/include/openid.hrl b/include/openid.hrl index 34e4534..0808e02 100644 --- a/include/openid.hrl +++ b/include/openid.hrl @@ -9,6 +9,7 @@ -define(GV(E, P), proplists:get_value(E, P)). -define(GVD(E, P, D), proplists:get_value(E, P, D)). -define(DBG(Term), io:format("~p: ~p~n", [self(), Term])). +-define(XRI_GCTX_SYMBOLS, [$=, $@, $+, $$, $!, $(]). -record(openid_xrdservice, { types, diff --git a/src/openid_utils.erl b/src/openid_utils.erl index d126c26..864b186 100644 --- a/src/openid_utils.erl +++ b/src/openid_utils.erl @@ -7,9 +7,13 @@ %%%------------------------------------------------------------------- -module(openid_utils). --export([get_tags/2, get_tags/4, url_encode/1, url_decode/1, uri_encode/1, uri_decode/1]). +-export([get_tags/2, get_tags/4]). +-export([url_encode/1, url_decode/1]). +-export([uri_encode/1, uri_decode/1]). +-export([normalize_id/1, normalize_http/1]). -include("openid.hrl"). +-include("deps/ibrowse/src/ibrowse.hrl"). get_tags(Content, Tag) -> find_tags(Content, {[], Tag, none, none}). @@ -64,6 +68,104 @@ check_val(V, V, PropList, Tail, {Buffer,Tag,Key,Val})-> check_val(_, _, _, Tail, State) -> find_tags(Tail, State). +normalize_id(Identifier) -> + Max = 1000000000, + Scheme = lists:sublist(Identifier, 8), + case string:to_lower(Scheme) of + "xri://" ++ _ -> + lists:sublist(Identifier, 7, Max); + "http://" ++ _ -> + Components = lists:sublist(Identifier, 8, Max), + normalize_http("http://" ++ Components); + "https://" ++ _ -> + Components = lists:sublist(Identifier, 9, Max), + normalize_http("https://" ++ Components); + [H|_] -> + case lists:member(H, ?XRI_GCTX_SYMBOLS) of + true -> Identifier; + false -> normalize_http("http://" ++ Identifier) + end + end. + +normalize_http(URL) -> + #url{host=Host, port=Port, username=Username, password=Password, path=Path, protocol=Protocol} = ibrowse_lib:parse_url(URL), + NewProtocol = atom_to_list(Protocol) ++ "://", + NewCreds = case {Username, Password} of + {undefined, undefined} -> ""; + {Username, ""} -> Username ++ "@"; + {Username, Password} -> Username ++ ":" ++ Password ++ "@" + end, + NewHost = normalize_host(Host), + NewPort = case {Protocol, Port} of + {http, 80} -> ""; + {https, 443} -> ""; + _ -> ":" ++ integer_to_list(Port) + end, + NewPath = normalize_path(Path), + NewProtocol ++ NewCreds ++ NewHost ++ NewPort ++ [$/|NewPath]. + +normalize_host(Host) when is_list(Host) -> + [ normalize_host(C) || C <- Host ]; +normalize_host(C) when is_integer(C) andalso (C >= $A) andalso (C =< $Z) -> + C + 32; +normalize_host(C) when is_integer(C) -> C. + +normalize_path([]) -> ""; +normalize_path(Path) when is_list(Path) -> + FragFreePath = remove_path_fragment(Path), + {BarePath, QueryString} = lists:splitwith(fun(X) -> X =/= $? end, FragFreePath), + FinalSlash = hd(lists:reverse(BarePath)) =:= $/, + NewQueryString = normalise_querystring(QueryString), + Segments = string:tokens(BarePath, "/"), + DotFreeSegments = remove_dot_segments(Segments), + PESegments = pe_normalise_segments(DotFreeSegments), + NewPath = string:join(PESegments, "/"), + case NewPath of + [] -> "" ++ NewQueryString; + NewPath when FinalSlash -> NewPath ++ "/" ++ NewQueryString; + NewPath -> NewPath ++ NewQueryString + end. + +remove_path_fragment(Path) -> remove_path_fragment(Path, []). +remove_path_fragment([$#|_], SoFar) -> lists:reverse(SoFar); +remove_path_fragment([], SoFar) -> lists:reverse(SoFar); +remove_path_fragment([H|T], SoFar) -> remove_path_fragment(T, [H|SoFar]). + +remove_dot_segments(Segments) -> + remove_dot_segments([], Segments). + +remove_dot_segments(Path, []) -> + lists:reverse(Path); +remove_dot_segments([_|Path], [".."|Rest]) -> + remove_dot_segments(Path, Rest); +remove_dot_segments(Path, ["."|Rest]) -> + remove_dot_segments(Path, Rest); +remove_dot_segments(Path, [Seg|Rest]) -> + remove_dot_segments([Seg|Path], Rest). + +pe_normalise_segments(Segments) when is_list(Segments) -> + [ pe_normalise_segment(Segment) || Segment <- Segments ]. +pe_normalise_segment(Segment) -> + RemovePE = uri_decode(Segment), + openid_utils:uri_encode(RemovePE). + +normalise_querystring("") -> ""; +normalise_querystring([$?|QS]) -> + Params = string:tokens(QS, "&"), + NewParams = lists:foldr( + fun(Param, Acc) -> + NewParam = case lists:splitwith(fun(X) -> X =/= $= end, Param) of + {Key, ""} -> uri_encode(uri_decode(Key)); + {Key, [$=|Value]} -> + NewKey = uri_encode(uri_decode(Key)), + NewValue = uri_encode(uri_decode(Value)), + NewKey ++ "=" ++ NewValue + end, + [NewParam|Acc] + end, [], Params), + NewQS = string:join(NewParams, "&"), + "?" ++ NewQS. + %% Sourced with permission from Tim's repo at %% http://github.com/tim/erlang-percent-encoding/blob/master/src/percent.erl diff --git a/src/yadis.erl b/src/yadis.erl index ddb69cf..f83bdbc 100644 --- a/src/yadis.erl +++ b/src/yadis.erl @@ -7,7 +7,7 @@ %%%------------------------------------------------------------------- -module(yadis). --export([normalize/1, retrieve/1, test/0]). +-export([retrieve/1, test/0]). -include("openid.hrl"). -include_lib("xmerl/include/xmerl.hrl"). @@ -23,23 +23,13 @@ %% API %% ------------------------------------------------------------ -normalize("xri://" ++ Identifier) -> {Identifier, true}; -normalize([$=|_]=Identifier) -> {Identifier, true}; -normalize([$@|_]=Identifier) -> {Identifier, true}; -normalize([$+|_]=Identifier) -> {Identifier, true}; -normalize([$$|_]=Identifier) -> {Identifier, true}; -normalize([$!|_]=Identifier) -> {Identifier, true}; -normalize([$(|_]=Identifier) -> {Identifier, true}; -normalize("http://" ++ Tail) -> {strip_fragment("http://" ++ Tail), false}; -normalize("https://" ++ Tail) -> {strip_fragment("https://" ++ Tail), false}; -normalize(PartialURL) -> {strip_fragment("http://" ++ PartialURL), false}. - retrieve(Identifier) -> application:start(inets), application:start(ssl), - {Normalized, IsXRI} = normalize(Identifier), + Normalized = openid_utils:normalize_id(Identifier), + IsXRI = lists:member(hd(Normalized), ?XRI_GCTX_SYMBOLS), URL = case IsXRI of true -> resolve(Normalized); @@ -77,13 +67,6 @@ retrieve(Identifier) -> resolve(Identifier) -> "http://xri.net/" ++ Identifier ++ "?_xrd_r=application/xrds+xml". -strip_fragment(URL) -> strip_fragment(URL, []). - -strip_fragment([$#|_], SoFar) -> lists:reverse(SoFar); -strip_fragment([], SoFar) -> lists:reverse(SoFar); -strip_fragment([H|T], SoFar) -> strip_fragment(T, [H|SoFar]). - - handle_response(none, Headers, Body) -> get_xrds(?GVD("content-type", Headers, none), Body);