%% -*- coding: utf-8 -*- %% @doc Punycode ([RFC 3492](http://tools.ietf.org/html/rfc3492)) implementation. -module(punycode). -export([encode/1, decode/1]). -define(BASE, 36). -define(TMIN, 1). -define(TMAX, 26). -define(SKEW, 38). -define(DAMP, 700). -define(INITIAL_BIAS, 72). -define(INITIAL_N, 128). -define(DELIMITER, $-). -define(MAX, 1 bsl 32 - 1). %% @doc Convert Unicode to Punycode. %% %% exit with an overflow error on overflow, which can only happen on inputs %% that would take more than 63 encoded bytes, the DNS limit on domain name labels. -spec encode(string()) -> string(). encode(Input) -> Output0 = lists:filtermap(fun (C) when C < 16#80 -> {true, C}; (_) -> false end, Input), B = length(Output0), Output = case B > 0 of true -> Output0 ++ [?DELIMITER]; false -> Output0 end, H = B, encode(Input, Output, H, B, ?INITIAL_N, 0, ?INITIAL_BIAS). encode(Input, Output, H, B, N, Delta, Bias) when H < length(Input) -> M = lists:min(lists:filter(fun(C) -> C >= N end, Input)), Delta1 = case (M - N) > ((?MAX - Delta) / (H +1)) of false -> Delta + (M - N) * (H + 1); true -> exit(oveflow) end, {Output2, H2, Delta2, N2, Bias2} = encode1(Input, Output, H, B, M, Delta1, Bias), encode(Input, Output2, H2, B, N2, Delta2, Bias2); encode(_, Output, _, _, _, _, _) -> Output. encode1([C|Rest], Output, H, B, N, Delta, Bias) when C < N -> Delta2 = Delta + 1, case Delta2 of 0 -> exit(oveflow); _ -> encode1(Rest, Output, H, B, N, Delta2, Bias) end; encode1([C|Rest], Output, H, B, N, Delta, Bias) when C == N -> encode2(Rest, Output, H, B, N, Delta, Bias, Delta, ?BASE); encode1([_|Rest], Output, H, B, N, Delta, Bias) -> encode1(Rest, Output, H, B, N, Delta, Bias); encode1([], Output, H, _B, N, Delta, Bias) -> {Output, H, Delta + 1, N +1, Bias}. encode2(Rest, Output, H, B, N, Delta, Bias, Q, K) -> T = if K =< Bias -> ?TMIN; K >= (Bias + ?TMAX) -> ?TMAX; true -> K - Bias end, case Q < T of true -> CodePoint = to_digit(Q), Output2 = Output ++ [CodePoint], Bias2 = adapt(Delta, H +1, H == B), Delta2 = 0, H2 = H + 1, encode1(Rest, Output2, H2, B, N, Delta2, Bias2); false -> CodePoint = to_digit(T + ((Q - T) rem (?BASE - T))), Output2 = Output ++ [CodePoint], Q2 = (Q - T) div (?BASE - T), encode2(Rest, Output2, H, B, N, Delta, Bias, Q2, K + ?BASE) end. to_digit(V) when V >= 0, V =< 25 -> V + $a; to_digit(V) when V >= 26, V =< 35 -> V - 26 + $0; to_digit(_) -> exit(badarg). %% @doc Convert Punycode to Unicode. %% exit with an overflow or badarg errors if malformed or overflow. %% Overflow can only happen on inputs that take more than 63 encoded bytes, %% the DNS limit on domain name labels. -spec decode(string()) -> string(). decode(Input) -> {Output, Input2} = case string:rstr(Input, [?DELIMITER]) of 0 -> {"", Input}; Pos -> {lists:sublist(Input, Pos - 1), lists:sublist(Input, Pos + 1, length(Input) )} end, decode(Input2, Output, ?INITIAL_N, ?INITIAL_BIAS, 0). decode([], Output, _, _, _) -> Output; decode(Input, Output, N, Bias, I) -> decode(Input, Output, N, Bias, I, I, 1, ?BASE). decode([C|Rest], Output, N, Bias, I0, OldI, Weight, K) -> Digit = digit(C), I1 = case Digit > ((?MAX - I0 ) div Weight) of false -> I0 + (Digit * Weight); true -> exit(overflow) end, T = if K =< Bias -> ?TMIN; K >= (Bias + ?TMAX) -> ?TMAX; true -> K - Bias end, case Digit < T of true -> Len = length(Output), Bias2 = adapt(I1 - OldI, Len + 1, (OldI =:= 0)), {N2, I2}= case (I1 div (Len +1)) > (?MAX - N) of false -> {N + (I1 div (Len + 1)), I1 rem (Len + 1)}; true -> exit(overflow) end, Output2 = insert(Output, N2, [], I2), decode(Rest, Output2, N2, Bias2, I2+1); false -> case Weight > (?MAX div (?BASE - T)) of false -> decode(Rest, Output, N, Bias, I1, OldI, Weight * (?BASE - T), K + ?BASE); true -> exit(overflow) end end. insert(Tail, CP, Head, 0) -> Head ++ [CP | Tail]; insert([], _CP, _Head, I) when I > 0-> exit(overflow); insert([C | Tail], CP, Head, I) -> insert(Tail, CP, Head ++ [C], I - 1). digit(C) when C >= $0, C =< $9 -> C - $0 + 26; digit(C) when C >= $A, C =< $Z -> C - $A; digit(C) when C >= $a, C =< $z -> C - $a; digit(_) -> exit(badarg). adapt(Delta, NumPoints, FirstTime) -> Delta2 = case FirstTime of true -> Delta div ?DAMP; false -> Delta div 2 end, adapt(Delta2 + (Delta2 div NumPoints), 0). adapt(Delta, K) -> case Delta > (((?BASE - ?TMIN) * ?TMAX) div 2) of true -> adapt(Delta div (?BASE - ?TMIN), K + ?BASE); false -> K + (((?BASE - ?TMIN + 1) * Delta) div (Delta + ?SKEW)) end.