package URI::irc; # draft-butcher-irc-url-04 use strict; use warnings; our $VERSION = '5.31'; use parent 'URI::_login'; use overload ( '""' => sub { $_[0]->as_string }, '==' => sub { URI::_obj_eq(@_) }, '!=' => sub { !URI::_obj_eq(@_) }, fallback => 1, ); sub default_port { 6667 } # ircURL = ircURI "://" location "/" [ entity ] [ flags ] [ options ] # ircURI = "irc" / "ircs" # location = [ authinfo "@" ] hostport # authinfo = [ username ] [ ":" password ] # username = *( escaped / unreserved ) # password = *( escaped / unreserved ) [ ";" passtype ] # passtype = *( escaped / unreserved ) # entity = [ "#" ] *( escaped / unreserved ) # flags = ( [ "," enttype ] [ "," hosttype ] ) # /= ( [ "," hosttype ] [ "," enttype ] ) # enttype = "," ( "isuser" / "ischannel" ) # hosttype = "," ( "isserver" / "isnetwork" ) # options = "?" option *( "&" option ) # option = optname [ "=" optvalue ] # optname = *( ALPHA / "-" ) # optvalue = optparam *( "," optparam ) # optparam = *( escaped / unreserved ) # XXX: Technically, passtype is part of the protocol, but is rarely used and # not defined in the RFC beyond the URL ABNF. # Starting the entity with /# is okay per spec, but it needs to be encoded to # %23 for the URL::_generic::path operations to parse correctly. sub _init { my $class = shift; my $self = $class->SUPER::_init(@_); $$self =~ s|^((?:[^:/?\#]+:)?(?://[^/?\#]*)?)/\#|$1/%23|s; $self; } # Return the /# form, since this is most common for channel names. sub path { my $self = shift; my ($new) = @_; $new =~ s|^/\#|/%23| if (@_ && defined $new); my $val = $self->SUPER::path(@_ ? $new : ()); $val =~ s|^/%23|/\#|; $val; } sub path_query { my $self = shift; my ($new) = @_; $new =~ s|^/\#|/%23| if (@_ && defined $new); my $val = $self->SUPER::path_query(@_ ? $new : ()); $val =~ s|^/%23|/\#|; $val; } sub as_string { my $self = shift; my $val = $self->SUPER::as_string; $val =~ s|^((?:[^:/?\#]+:)?(?://[^/?\#]*)?)/%23|$1/\#|s; $val; } sub entity { my $self = shift; my $path = $self->path; $path =~ s|^/||; my ($entity, @flags) = split /,/, $path; if (@_) { my $new = shift; $new = '' unless defined $new; $self->path( '/'.join(',', $new, @flags) ); } return unless length $entity; $entity; } sub flags { my $self = shift; my $path = $self->path; $path =~ s|^/||; my ($entity, @flags) = split /,/, $path; if (@_) { $self->path( '/'.join(',', $entity, @_) ); } @flags; } sub options { shift->query_form(@_) } sub canonical { my $self = shift; my $other = $self->SUPER::canonical; # Clean up the flags my $path = $other->path; $path =~ s|^/||; my ($entity, @flags) = split /,/, $path; my @clean = map { $_ eq 'isnick' ? 'isuser' : $_ } # convert isnick->isuser map { lc } # NOTE: Allow flags from draft-mirashi-url-irc-01 as well grep { /^(?:is(?:user|channel|server|network|nick)|need(?:pass|key))$/i } @flags ; # Only allow the first type of each category, per the Butcher draft my ($enttype) = grep { /^is(?:user|channel)$/ } @clean; my ($hosttype) = grep { /^is(?:server|network)$/ } @clean; my @others = grep { /^need(?:pass|key)$/ } @clean; my @new = ( $enttype ? $enttype : (), $hosttype ? $hosttype : (), @others, ); unless (join(',', @new) eq join(',', @flags)) { $other = $other->clone if $other == $self; $other->path( '/'.join(',', $entity, @new) ); } $other; } 1;