The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package DJabberd::JID;
use strict;
use DJabberd::Util qw(exml);
use Digest::SHA1;

# Configurable via 'CaseSensitive' config option
our $CASE_SENSITIVE = 0;

use overload
    '""' => \&as_string_exml;

use constant NODE       => 0;
use constant DOMAIN     => 1;
use constant RES        => 2;
use constant AS_STRING  => 3;
use constant AS_BSTRING => 4;
use constant AS_STREXML => 5;

# Stringprep functions for converting to canonical form
use Unicode::Stringprep;
use Unicode::Stringprep::Mapping;
use Unicode::Stringprep::Prohibited;
my $nodeprep = Unicode::Stringprep->new(
    3.2,
    [
        \@Unicode::Stringprep::Mapping::B1,
        \@Unicode::Stringprep::Mapping::B2,
    ],
    'KC',
    [
        \@Unicode::Stringprep::Prohibited::C11,
        \@Unicode::Stringprep::Prohibited::C12,
        \@Unicode::Stringprep::Prohibited::C21,
        \@Unicode::Stringprep::Prohibited::C22,
        \@Unicode::Stringprep::Prohibited::C3,
        \@Unicode::Stringprep::Prohibited::C4,
        \@Unicode::Stringprep::Prohibited::C5,
        \@Unicode::Stringprep::Prohibited::C6,
        \@Unicode::Stringprep::Prohibited::C7,
        \@Unicode::Stringprep::Prohibited::C8,
        \@Unicode::Stringprep::Prohibited::C9,
        [
            0x0022, undef, # "
            0x0026, undef, # &
            0x0027, undef, # '
            0x002F, undef, # /
            0x003A, undef, # :
            0x003C, undef, # <
            0x003E, undef, # >
            0x0040, undef, # @
        ]
    ],
    1,
);
my $nameprep = Unicode::Stringprep->new(
    3.2,
    [
        \@Unicode::Stringprep::Mapping::B1,
        \@Unicode::Stringprep::Mapping::B2,
    ],
    'KC',
    [
        \@Unicode::Stringprep::Prohibited::C12,
        \@Unicode::Stringprep::Prohibited::C22,
        \@Unicode::Stringprep::Prohibited::C3,
        \@Unicode::Stringprep::Prohibited::C4,
        \@Unicode::Stringprep::Prohibited::C5,
        \@Unicode::Stringprep::Prohibited::C6,
        \@Unicode::Stringprep::Prohibited::C7,
        \@Unicode::Stringprep::Prohibited::C8,
        \@Unicode::Stringprep::Prohibited::C9,
    ],
    1,
);
my $resourceprep = Unicode::Stringprep->new(
    3.2,
    [
        \@Unicode::Stringprep::Mapping::B1,
    ],
    'KC',
    [
        \@Unicode::Stringprep::Prohibited::C12,
        \@Unicode::Stringprep::Prohibited::C21,
        \@Unicode::Stringprep::Prohibited::C22,
        \@Unicode::Stringprep::Prohibited::C3,
        \@Unicode::Stringprep::Prohibited::C4,
        \@Unicode::Stringprep::Prohibited::C5,
        \@Unicode::Stringprep::Prohibited::C6,
        \@Unicode::Stringprep::Prohibited::C7,
        \@Unicode::Stringprep::Prohibited::C8,
        \@Unicode::Stringprep::Prohibited::C9,
    ],
    1,
);


# returns DJabberd::JID object, or undef on failure due to invalid format
sub new {
    #my ($class, $jidstring) = @_;

    # The following regex is loosely based on the EBNF grammar in
    # JEP-0029. This JEP has actually been retracted, but seems to be
    # the only reasonable spec for JID syntax.

    # NOTE: Currently this only supports US-ASCII characters.

    return undef unless $_[1] && $_[1] =~
        m!^(?: ([\x29\x23-\x25\x28-\x2E\x30-\x39\x3B\x3D\x3F\x41-\x7E]{1,1023}) \@)? # $1: optional node
           ([a-zA-Z0-9\.\-]{1,1023})                                                 # $2: domain
           (?: /(.{1,1023})   )?                                           # $3: optional resource
           $!x;

    # If we're in case-sensitive mode, for backwards-compatibility,
    # then skip stringprep
    return bless [ $1, $2, $3 ], $_[0] if $DJabberd::JID::CASE_SENSITIVE;

    # Stringprep uses regexes, so store these away first
    my ($node, $host, $res) = ($1, $2, $3);

    return eval {
        bless [
            defined $node ? $nodeprep->($node) : undef,
            $nameprep->($host),
            defined $res  ? $resourceprep->($res) : undef,
        ], $_[0]
    };
}

sub is_bare {
    return $_[0]->[RES] ? 0 : 1;
}

sub node {
    return $_[0]->[NODE];
}

sub domain {
    return $_[0]->[DOMAIN];
}

sub resource {
    return $_[0]->[RES];
}

sub eq {
    my ($self, $jid) = @_;
    return $jid && $self->as_string eq $jid->as_string;
}

sub as_string {
    my $self = $_[0];
    return $self->[AS_STRING] ||=
        join('',
             ($self->[NODE] ? ($self->[NODE], '@') : ()),
             $self->[DOMAIN],
             ($self->[RES] ? ('/', $self->[RES]) : ()));
}

sub as_string_exml {
    my $self = $_[0];
    return $self->[AS_STREXML] ||=
        exml($self->as_string);
}

sub as_bare_string {
    my $self = $_[0];
    return $self->[AS_BSTRING] ||=
        join('',
             ($self->[NODE] ? ($self->[NODE], '@') : ()),
             $self->[DOMAIN]);
}

sub rand_resource {
    Digest::SHA1::sha1_hex(rand() . rand() . rand());
}

1;