OSDN Git Service

*** empty log message ***
[keitairc/keitairc.git] / keitairc
index 741c161..d51379d 100755 (executable)
--- a/keitairc
+++ b/keitairc
@@ -1,14 +1,14 @@
 #!/usr/bin/perl
 # keitairc
-# $Id: keitairc,v 1.20 2004-06-07 01:27:57 morimoto Exp $
+# $Id: keitairc,v 1.30 2006-08-03 07:19:47 morimoto Exp $
 #
-# Copyright (c) 2003 Jun Morimoto <morimoto@xantia.citroen.org>
+# Copyright (c) Jun Morimoto <morimoto@mrmt.net>
 # This program is covered by the GNU General Public License 2
 #
 # Depends: libjcode-pm-perl, libpoe-component-irc-perl,
 #   liburi-perl, libwww-perl, libappconfig-perl
 
-my $rcsid = q$Id: keitairc,v 1.20 2004-06-07 01:27:57 morimoto Exp $;
+my $rcsid = q$Id: keitairc,v 1.30 2006-08-03 07:19:47 morimoto Exp $;
 my ($version) = $rcsid =~ m#,v ([0-9.]+)#;
 
 use strict;
@@ -21,6 +21,10 @@ use URI::Escape;
 use HTTP::Response;
 use AppConfig qw(:argcount);
 
+use constant true => 1;
+use constant false => 0;
+use constant cookie_ttl => 86400*3;  # 3 days
+
 my $config = AppConfig->new(
                            {
                                CASE => 1,
@@ -30,6 +34,7 @@ my $config = AppConfig->new(
                            },
                            qw(irc_nick irc_username irc_desc
                               irc_server irc_port irc_password
+                              au_subscriber_id au_pcsv use_cookie
                               web_port web_title web_lines web_root
                               web_username web_password show_newmsgonly)
                            );
@@ -46,6 +51,9 @@ if(defined $config->web_root){
 # join \e$B$7$F$$$k%A%c%M%k$NL>>N$r5-O?$9$k%O%C%7%e\e(B
 my %channel_name;
 
+# join \e$B$7$F$$$k%A%c%M%k$NL>>N$r5-O?$9$k%O%C%7%e\e(B
+my %topic;
+
 # \e$B%A%c%M%k$N2qOCFbMF$r5-O?$9$k%O%C%7%e\e(B
 my (%channel_buffer, %channel_recent);
 
@@ -53,10 +61,10 @@ my (%channel_buffer, %channel_recent);
 my %mtime;
 
 # unread lines
-my %unread;
+my %unread_lines;
 
 # chk
-my ($send_chk, $update_chk);
+my ($message_added);
 
 # irc component
 POE::Component::IRC->new('keitairc');
@@ -66,6 +74,9 @@ POE::Session->new(
                  irc_part => \&on_irc_part,
                  irc_public => \&on_irc_public,
                  irc_notice => \&on_irc_notice,
+                 irc_topic => \&on_irc_topic,
+                 irc_332 => \&on_irc_topicraw,
+                 irc_ctcp_action => \&on_irc_ctcp_action,
                  );
 
 # web server component
@@ -100,8 +111,9 @@ sub on_irc_join{
 
     # chop off after the gap (bug workaround of madoka)
     $channel =~ s/ .*//;
+    my $canon_channel = &canon_name($channel);
 
-    $channel_name{$channel}++;
+    $channel_name{$canon_channel} = $channel;
     unless ($who eq $config->irc_nick) {
       &add_message($channel, undef, "$who joined");
     }
@@ -114,9 +126,10 @@ sub on_irc_part{
 
     # chop off after the gap (bug workaround of POE::Filter::IRC)
     $channel =~ s/ .*//;
+    my $canon_channel = &canon_name($channel);
 
     if ($who eq $config->irc_nick) {
-       delete $channel_name{$channel};
+       delete $channel_name{$canon_channel};
     } else {
        &add_message($channel, undef, "$who leaves");
     }
@@ -141,6 +154,31 @@ sub on_irc_notice{
 }
 
 ################################################################
+sub on_irc_topic{
+    my ($kernel, $who, $channel, $topic) = @_[KERNEL, ARG0 .. ARG2];
+    $who =~ s/!.*//;
+    $topic = Jcode->new($topic, 'jis')->euc;
+    &add_message($channel, undef, "$who set topic: $topic");
+    $topic{&canon_name($channel)} = $topic;
+}
+
+################################################################
+sub on_irc_topicraw{
+    my ($kernel, $raw) = @_[KERNEL, ARG1];
+    my ($channel, $topic) = split(/ :/, $raw, 2);
+    $topic{&canon_name($channel)} = $topic;
+}
+
+################################################################
+sub on_irc_ctcp_action{
+    my ($kernel, $who, $channel, $msg) = @_[KERNEL, ARG0 .. ARG2];
+    $who =~ s/!.*//;
+    $channel = $channel->[0];
+    $msg = sprintf('* %s %s', $who, Jcode->new($msg, 'jis')->euc);
+    &add_message($channel, '', $msg);
+}
+
+################################################################
 # $msg \e$B$O\e(B EUC \e$B$K$J$C$F$$$k$O$:\e(B
 # $channel \e$B$O\e(B jis \e$B$G$-$F$k$>\e(B
 sub add_message{
@@ -153,33 +191,34 @@ sub add_message{
       $message = sprintf('%s %s', &now, $msg);
     }
 
-    my @tmp = split("\n", $channel_buffer{$channel});
+    my $canon_channel = &canon_name($channel);
+    my @tmp = split("\n", $channel_buffer{$canon_channel});
     push @tmp, $message;
 
-    my @tmp2 = split("\n", $channel_recent{$channel});
+    my @tmp2 = split("\n", $channel_recent{$canon_channel});
     push @tmp2, $message;
 
     if(@tmp > $config->web_lines){
-       $channel_buffer{$channel} =
+       $channel_buffer{$canon_channel} =
                join("\n", splice(@tmp, -$config->web_lines));
     }else{
-       $channel_buffer{$channel} = join("\n", @tmp);
+       $channel_buffer{$canon_channel} = join("\n", @tmp);
     }
 
     if(@tmp2 > $config->web_lines){
-       $channel_recent{$channel} =
-               join("\n", splice(@tmp2, -$config->web_lines));
+       $channel_recent{$canon_channel} =
+               join("\n", @tmp2[1 .. $config->web_lines]);
     }else{
-       $channel_recent{$channel} = join("\n", @tmp2);
+       $channel_recent{$canon_channel} = join("\n", @tmp2);
     }
 
-    $mtime{$channel} = time;
+    $mtime{$canon_channel} = time;
 
     # unread lines
-    $unread{$channel} = scalar(@tmp2);
+    $unread_lines{$canon_channel} = scalar(@tmp2);
 
-    if ($unread{$channel} > $config->web_lines) {
-       $unread{$channel} = $config->web_lines;
+    if($unread_lines{$canon_channel} > $config->web_lines){
+        $unread_lines{$canon_channel} = $config->web_lines;
     }
 }
 
@@ -192,9 +231,9 @@ sub now{
 ################################################################
 sub escape{
     local($_) = shift;
-    s/&/&amp;/;
-    s/>/&gt;/;
-    s/</&lt;/;
+    s/&/&amp;/g;
+    s/>/&gt;/g;
+    s/</&lt;/g;
     $_;
 }
 
@@ -213,10 +252,12 @@ sub label{
 sub index_page{
     my $buf;
     my $accesskey = 1;
+    my $channel;
 
-    for my $channel (sort {
+    for my $canon_channel (sort {
        $mtime{$b} <=> $mtime{$a};
     }(keys(%channel_name))){
+       $channel = $channel_name{$canon_channel};
 
        $buf .= &label($accesskey);
 
@@ -236,16 +277,25 @@ sub index_page{
        $accesskey++;
 
        # \e$BL$FI9T?t\e(B
-       if($unread{$channel} > 0){
-               $buf .= sprintf(' <a href="%s%s.update">%d</a>',
+       if($unread_lines{$canon_channel}){
+               $buf .= sprintf(' <a href="%s%s,recent">%s</a>',
                                $docroot,
                                uri_escape($channel),
-                               $unread{$channel});
+                               $unread_lines{$canon_channel});
        }
        $buf .= '<br>';
     }
 
     $buf .= qq(0 <a href="$docroot" accesskey="0">refresh list</a><br>);
+
+    if(grep($unread_lines{$_}, keys %unread_lines)){
+      $buf .= qq(* <a href="$docroot,recent" accesskey="*">recent</a><br>);
+    }
+
+    if(keys %topic){
+      $buf .= qq(# <a href="$docroot,topics" accesskey="#">topics</a><br>);
+    }
+
     $buf .= qq( - keitairc $version);
     $buf;
 }
@@ -267,6 +317,27 @@ sub compact_channel_name{
 }
 
 ################################################################
+sub canon_name{
+    local($_) = shift;
+
+    tr/A-Z[\\]^/a-z{|}~/;
+
+    $_;
+}
+
+################################################################
+sub link_url{
+       my $url = shift;
+       my @buf;
+       push @buf, sprintf('<a href="%s">%s</a>', $url, $url);
+       if(defined $config->au_pcsv && $ENV{HTTP_USER_AGENT} =~ /^KDDI-/){
+               push @buf, sprintf('<a href="device:pcsiteviewer?url=%s">[PCSV]</a>', $url);
+       }
+       push @buf, sprintf('<a href="http://www.google.com/gwt/n?u=%s&hl=ja&mrestrict=xhtml|chtml&lr=&inlang=ja&client=ms-kddi-jp">[GWT]</a>', uri_escape($url));
+       join(' ', @buf);
+}
+
+################################################################
 sub render{
     local($_);
     my @buf;
@@ -279,8 +350,8 @@ sub render{
 
        $_ = &escape($_);
 
-       unless(s,\b(https?://[!-;=-\177]+)\b,<a href="$1">$1</a>,g){
-           unless(s|\b(www\.[!-\177]+)\b|<a href="http://$1">$1</a>|g){
+       unless(s|\b(https?://[!-;=-\177]+)\b|link_url($1)|eg){
+           unless(s|\b(www\.[!-\177]+)\b|link_url($1)|eg){
                # phone to
                unless(s|\b(0\d{1,3})([-(]?)(\d{2,4})([-)]?)(\d{4})\b|<a href="tel:$1$3$5">$1$2$3$4$5</a>|g){
                    s|\b(\w[\w.+=-]+\@[\w.-]+[\w]\.[\w]{2,4})\b|<a href="mailto:$1">$1</a>|g;
@@ -310,17 +381,38 @@ sub on_web_request{
        return;
     }
 
-    if(defined($config->web_username)){
-       unless($request->headers->authorization_basic eq
-              $config->web_username . ':' . $config->web_password){
+    # cookie
+    my $cookie_authorized;
+    if($config->use_cookie){
+      my %cookie;
+      for(split(/; */, $request->header('Cookie'))){
+       my ($name, $value) = split(/=/);
+       $value =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('C', hex($1))/eg;
+        $cookie{$name} = $value;
+      }
+
+      if($cookie{username} eq $config->web_username &&
+        $cookie{passwd} eq $config->web_password){
+       $cookie_authorized = true;
+      }
+    }
 
+    # authorization
+    unless($cookie_authorized){
+      unless(defined($config->au_subscriber_id) &&
+            $request->header('x-up-subno') eq $config->au_subscriber_id){
+       if(defined($config->web_username)){
+         unless($request->headers->authorization_basic eq
+                $config->web_username . ':' . $config->web_password){
            my $response = HTTP::Response->new(401);
            $response->push_header(WWW_Authenticate =>
                                   qq(Basic Realm="keitairc"));
            $heap->{client}->put($response);
            $kernel->yield('shutdown');
            return;
+         }
        }
+      }
     }
 
     my $uri = $request->uri;
@@ -343,24 +435,73 @@ sub on_web_request{
                              Jcode->new($message)->jis);
            &add_message($channel, $config->irc_nick,
                         Jcode->new($message)->euc);
-
-           # set flag for "send message"
-           $send_chk = 1;
+           $message_added = true;
        }
     }
 
+    # store and remove attached options from uri
+    my %option;
+    {
+      my @opts = split(',', $uri);
+      shift @opts;
+      grep($option{$_} = $_, @opts);
+      $uri =~ s/,.*//;
+    }
+
     if($uri eq '/'){
-       $content .= '<title>' . $config->web_title . '</title>';
-       $content .= '</head>';
-       $content .= '<body>';
+      $content .= '<title>' . $config->web_title . '</title>';
+      $content .= '</head>';
+      $content .= '<body>';
+
+      if($option{recent}){
+        # recent messages on every channel
+       for my $canon_channel (sort keys %channel_name){
+         my $channel = $channel_name{$canon_channel};
+         if(length($channel) &&
+            length($channel_recent{$canon_channel})){
+           $content .= '<b>' . Jcode->new($channel_name{$canon_channel})->euc . '</b>';
+           $content .= sprintf(' <a href="%s%s">more..</a><br>',
+                               $docroot, uri_escape($channel));
+           $content .= &render($channel_recent{$canon_channel});
+           $unread_lines{$canon_channel} = 0;
+           $channel_recent{$canon_channel} = '';
+           $content .= '<hr>';
+         }
+       }
+        $content .= qq(<a accesskey="8" href="$docroot">ch list[8]</a>);
+      }elsif($option{topics}){
+        # topic on every channel
+       for my $canon_channel (sort keys %channel_name){
+         my $channel = $channel_name{$canon_channel};
+         if(length $channel){
+           $content .= sprintf(' <a href="%s%s">%s</a><br>',
+                               $docroot, uri_escape($channel),
+                               Jcode->new($channel_name{$canon_channel})->euc);
+           $content .= &escape(Jcode->new($topic{$canon_channel})->euc);
+           $content .= '<br>';
+         }
+       }
+        $content .= qq(<br><a accesskey="8" href="$docroot">ch list[8]</a>);
+      }else{
+        # channel list
        $content .= &index_page;
+      }
     }else{
+        # channel conversation
        $uri =~ s|^/||;
 
-       $update_chk = ($uri =~ /.*.update/);
-       if ($update_chk eq 1) {
-               $uri =~ s/.update//;
-       }
+       # RFC 2811:
+       # Apart from the the requirement that the first character
+       # being either '&', '#', '+' or '!' (hereafter called "channel
+       # prefix"). The only restriction on a channel name is that it
+       # SHALL NOT contain any spaces (' '), a control G (^G or ASCII
+       # 7), a comma (',' which is used as a list item separator by
+       # the protocol).  Also, a colon (':') is used as a delimiter
+       # for the channel mask.  The exact syntax of a channel name is
+       # defined in "IRC Server Protocol" [IRC-SERVER].
+       #
+       # so we use white space as separator character of channel name
+       # and command argument.
 
        my $channel = uri_unescape($uri);
 
@@ -375,42 +516,65 @@ sub on_web_request{
                            $docroot, uri_escape($channel));
        $content .= '<input type="text" name="m" size="10">';
        $content .= '<input type="submit" accesskey="1" value="OK[1]">';
-        $content .= qq(<a accesskey="8" href="$docroot">back[8]</a><br>);
-       # $content .= '<input type="submit" accesskey="1" value="&#63920;">';
+        $content .= qq(<a accesskey="8" href="$docroot">ch list[8]</a><br>);
        $content .= '</form>';
 
-       if(defined($channel_name{$channel})){
-           if(defined($channel_buffer{$channel}) &&
-              length($channel_buffer{$channel})){
+       my $canon_channel = &canon_name($channel);
+       if(defined($channel_name{$canon_channel})){
+           if(defined($channel_buffer{$canon_channel}) &&
+              length($channel_buffer{$canon_channel})){
                $content .= '<a accesskey="9" href="#2"></a>';
-               if ((($update_chk eq 1)||((defined $config->show_newmsgonly) && ($send_chk eq 1)))) {
-                 $content .= &render($channel_recent{$channel});
-                 $content .= sprintf('<a accesskey="5" href="%s%s">
-                       ..more[5]</a>', $docroot, uri_escape($channel));
+               if($option{recent} ||
+                  (defined($config->show_newmsgonly) && $message_added)){
+                 $content .= &render($channel_recent{$canon_channel});
+                 $content .= sprintf('<a accesskey="5" href="%s%s">more[5]</a>',
+                                     $docroot, uri_escape($channel));
                } else {
-                 $content .= &render($channel_buffer{$channel});
+                 $content .= &render($channel_buffer{$canon_channel});
                }
+               $content .= '<a accesskey="9" href="#2"></a>';
                $content .= '<a name="2"></a>';
            }else{
                $content .= 'no message here yet';
            }
        }else{
-           $content .= "no such channel";
+           $content .= 'no such channel';
        }
 
        # clear check flags
-       $send_chk = 0;
+       $message_added = false;
 
        # clear unread counter
-        $unread{$channel} = 0;
+        $unread_lines{$canon_channel} = 0;
 
        # clear recent messages buffer
-       $channel_recent{$channel} = '';
+       $channel_recent{$canon_channel} = '';
     }
 
     $content .= '</body></html>';
 
     my $response = HTTP::Response->new(200);
+
+    if($config->use_cookie){
+      my ($sec, $min, $hour, $mday, $mon, $year, $wday) =
+       localtime(time + cookie_ttl);
+      my $expiration =
+       sprintf('%.3s, %.2d-%.3s-%.4s %.2d:%.2d:%.2d',
+               qw(Sun Mon Tue Wed Thu Fri Sat)[$wday],
+               $mday,
+               qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)[$mon],
+               $year + 1900,
+               $hour,
+               $min,
+               $sec);
+      $response->push_header('Set-Cookie',
+                            sprintf("username=%s; expires=%s; \n",
+                                    $config->web_username, $expiration));
+      $response->push_header('Set-Cookie',
+                            sprintf("passwd=%s; expires=%s; \n",
+                                    $config->web_password, $expiration));
+    }
+
     $response->push_header('Content-type', 'text/html; charset=Shift_JIS');
     $response->content(Jcode->new($content)->sjis);
     $heap->{client}->put($response);