This will encode a complete domain name into Punycode (see RFC 3490).

Update: Corrected small mistake, 7bit ends at \x7F not \x8F...

use IDNA::Punycode; use Net::IDN::Nameprep; use warnings; use strict; our $ACE = 'xn--'; sub punycode { my $utf8name = shift; my @nameparts = split /[\x{002E}\x{3002}\x{FF0E}\x{FF61}]/, $utf8n +ame; my $asciiname = ''; foreach my $part (@nameparts) { if ($part =~ /[^\x00-\x7F]/) { eval { $part = nameprep($part); }; if (my $e = $@) { die "Preparing domain part '$part' of domain '$utf8nam +e' failed with '$e'"; } } if ($part =~ /([\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F] +)/) { die "Illegal character $1 in domain part '$part' of domain + '$utf8name' found"; } if ($part =~ /^\x2D/ or $part =~ /\x2D$/) { die "Domain part '$part' of domain '$utf8name' may not sta +rt or end with a hyphen"; } if ($part =~ /[^\x00-\x7F]/) { if (substr($part, 0, length($ACE)) eq $ACE) { die "International domain part '$part' of domain '$utf +8name' may not start with ACE"; } eval { $part = encode_punycode($part); }; if (my $e = $@) { die "Converting domain part '$part' of domain '$utf8na +me' failed with '$e'"; } $part = $ACE . $part; } if (length($part) < 1 or length($part) > 63) { die "Domain part '$part' of domain '$utf8name' must be bet +ween 1 and 63 characters long"; } } $asciiname = join "\x{002E}", @nameparts; return $asciiname; }