OSDN Git Service

add maxlength to handle privmsg maxlength
[keitairc/keitairc.git] / keitairc
index d39a54d..fc7afd4 100755 (executable)
--- a/keitairc
+++ b/keitairc
@@ -1,17 +1,17 @@
 #!/usr/bin/perl
+# -*- mode: perl; coding: utf-8 -*-
 # keitairc
-# $Id: keitairc,v 1.36 2008-01-13 06:23:53 morimoto Exp $
-# $Source: /home/ishikawa/work/keitairc/tmp/keitairc/keitairc,v $
 #
-# Copyright (c) 2003-2008 Jun Morimoto <morimoto@mrmt.net>
+# Copyright (c) 2003-2010 Jun Morimoto <morimoto@mrmt.net>
 # This program is covered by the GNU General Public License 2
 #
 # Depends: libpoe-component-irc-perl,
 #   liburi-perl, libwww-perl, libappconfig-perl, libproc-daemon-perl,
 #   libhtml-template-perl
+#
+# 00location_receiver plugin use XML::Simple, so if you want to use it
+#    Depends: libxml-simple-perl
 
-use lib qw(lib /usr/share/keitairc/lib);
-use strict;
 use Encode;
 use POE;
 use POE::Filter::HTTPD;
@@ -20,7 +20,10 @@ use POE::Component::Server::TCP;
 use URI::Escape;
 use HTML::Template;
 use HTTP::Response;
-use Proc::Daemon;
+use HTTP::Status;
+
+use FindBin;
+use lib ("$FindBin::Bin/lib", '/usr/share/keitairc/lib');
 use Keitairc::Config;
 use Keitairc::View;
 use Keitairc::IrcBuffer;
@@ -28,23 +31,34 @@ use Keitairc::IrcCallback;
 use Keitairc::ClientInfo;
 use Keitairc::SessionManager;
 use Keitairc::Plugins;
+use Keitairc::Log;
+use strict;
+use warnings;
 
-our $cf = new Keitairc::Config('2.0b1', @ARGV);
-our $ib = new Keitairc::IrcBuffer({history => $cf->web_lines()});
-our $sm = new Keitairc::SessionManager({default_ttl => $cf->session_ttl()});
-our $pl = new Keitairc::Plugins({config => $cf});
+our $cf = new Keitairc::Config({version => '2.1a1', argv => \@ARGV});
 
 # daemonize
 if($cf->daemonize()){
-       Proc::Daemon::Init;
-       if(length $cf->pid_dir()){
-               if (open(PID, '> ' . $cf->pid_dir() . '/keitairc.pid')) {
-                       print PID $$, "\n";
-                       close(PID);
+       if (eval 'require Proc::Daemon') {
+               require Proc::Daemon;
+               Proc::Daemon::Init();
+               if(length $cf->pid_dir()){
+                       if (open(PID, '> ' . $cf->pid_dir() . '/' . $cf->pid_file())) {
+                               print PID $$, "\n";
+                               close(PID);
+                       }
                }
+               $poe_kernel->has_forked if ($poe_kernel->can('has_forked'));
+       } else {
+               warn('Proc::Daemon module is not installed, could not daemonize');
        }
 }
 
+our $log = new Keitairc::Log({config => $cf});
+our $ib = new Keitairc::IrcBuffer({history => $cf->web_lines()});
+our $sm = new Keitairc::SessionManager({default_ttl => $cf->session_ttl()});
+our $pl = new Keitairc::Plugins({config => $cf});
+
 # create irc component
 our $irc = POE::Component::IRC->spawn(
        Alias => 'keitairc_irc',
@@ -68,13 +82,19 @@ POE::Session->create(
                _start => \&Keitairc::IrcCallback::irc_start,
                autoping => \&Keitairc::IrcCallback::irc_autoping,
                connect => \&Keitairc::IrcCallback::irc_connect,
+               irc_registered => \&Keitairc::IrcCallback::irc_registered,
                irc_001 => \&Keitairc::IrcCallback::irc_001,
                irc_join => \&Keitairc::IrcCallback::irc_join,
                irc_part => \&Keitairc::IrcCallback::irc_part,
+               irc_quit => \&Keitairc::IrcCallback::irc_quit,
                irc_public => \&Keitairc::IrcCallback::irc_public,
                irc_notice => \&Keitairc::IrcCallback::irc_notice,
+               irc_mode => \&Keitairc::IrcCallback::irc_mode,
+               irc_nick => \&Keitairc::IrcCallback::irc_nick,
+               irc_msg => \&Keitairc::IrcCallback::irc_msg,
                irc_topic => \&Keitairc::IrcCallback::irc_topic,
                irc_332 => \&Keitairc::IrcCallback::irc_topicraw,
+               irc_352 => \&Keitairc::IrcCallback::irc_whoreply,
                irc_ctcp_action => \&Keitairc::IrcCallback::irc_ctcp_action,
                irc_disconnected => \&Keitairc::IrcCallback::irc_reconnect,
                irc_error => \&Keitairc::IrcCallback::irc_reconnect,
@@ -84,7 +104,7 @@ POE::Session->create(
 # create web server component
 POE::Component::Server::TCP->new(
        Alias => 'keitairc',
-       Port => $cf->web_port(),
+       Port => $cf->web_listen_port(),
        ClientFilter => 'POE::Filter::HTTPD',
        ClientInput => \&http_request);
 
@@ -102,8 +122,10 @@ sub http_request{
        # the responses as they are and finish up.
        if($request->isa('HTTP::Response')){
                $heap->{client}->put($request);
+               $log->log_error($request->as_string());
        }elsif(my $response = dispatch($request)){
                $heap->{client}->put($response);
+               $log->log_access($heap->{'remote_ip'}, $request, $response);
        }
 
        $kernel->yield('shutdown');
@@ -115,7 +137,13 @@ sub dispatch{
        my $uri = $request->uri();
        my $ci = new Keitairc::ClientInfo($request);
 
-       ::log_debug("dispatch: $uri");
+       $log->log_debug("dispatch: $uri");
+
+       {
+               # chop off $cf->web_root()
+               my $root = $cf->web_root();
+               $uri =~ s|$root|/|;
+       }
 
        if($uri eq '/'){
                return action_root($request);
@@ -129,18 +157,25 @@ sub dispatch{
                return action_login_icc($request);
        }
 
+       if($uri eq '/login_imodeid?guid=ON'){
+               return action_login_imodeid($request);
+       }
+
        for my $name ($pl->list_action_plugins()){
                if($uri =~ m|^/(S[a-zA-Z]{10})/$name/(.*)| ||
                   $uri =~ m|^/(S[a-zA-Z]{10})/$name$|){
                        if($sm->verify({session_id => $1, user_agent => $ci->user_agent()})){
                                return add_cookie($pl->{plugins}->{$name}->{action_imprementation}($request, $name, $1, $2), $1);
                        }
-                       return action_redirect_root($request);
+                       if ($ci->is_webkit() && $cf->webkit_newui()) {
+                               return action_error($request, 401);
+                       } else {
+                               return action_redirect_root($request);
+                       }
                }
        }
 
-       ::log("dispatch: don't know how to dispatch uri[$uri]");
-       return action_404($request);
+       return action_public($request, $uri) || action_error($request, 404);
 }
 
 ################################################################
@@ -159,48 +194,64 @@ sub add_cookie{
                        $hour,
                        $min,
                        $sec);
-       my $content = sprintf("sid=%s; expires=%s; \n", $session_id, $expiration);
+       my $content = sprintf("sid=%s; expires=%s; path=%s; \n", $session_id, $expiration, $cf->web_root());
        $response->push_header('Set-Cookie', $content);
        $response;
 }
 
 ################################################################
-# \92Ê\8fí\83\8d\83O\83C\83\93\82ÌPOST\90æ
-# \83p\83X\83\8f\81[\83h\82ð\83`\83F\83b\83N\82µ\82Ä
-# \8aÔ\88á\82Á\82Ä\82¢\82½\82ç / \82Ö\83\8a\83\93\83N\82µ\82Ä\8fI\82í\82è
-# \8d\87\82Á\82Ä\82¢\82½\82ç\83Z\83b\83V\83\87\83\93\82ð\94­\8ds\82µ /{SESSION}/index \82Ö
+# 通常ログインのPOST先
+# パスワードをチェックして
+# 間違っていたら / へリンクして終わり
+# 合っていたらセッションを発行し /{SESSION}/index へ
 sub action_login{
        my $request = shift;
        my $ci = new Keitairc::ClientInfo($request);
        my $content = $request->decoded_content();
        my ($password) = ($content =~ /^password=(.*)/);
 
-       ::log_debug("password [$password]");
-       ::log_debug("web_password [" . $cf->web_password() . "]");
+       $log->log_debug("password [$password]");
+       $log->log_debug("web_password [" . $cf->web_password() . "]");
 
        if($cf->web_password() eq $password){
-               my $s = $sm->add($ci->{header}->{user_agent}, $ci->serial_key());
+               my $s = $sm->add($ci->user_agent(), $ci->serial_key());
                my $view = new Keitairc::View($cf, $ci, $s->{id});
-               return $view->redirect("/$s->{id}/index");
+               if ($ci->is_webkit() && $cf->webkit_newui()) {
+                       return add_cookie($view->redirect('/'), $s->{id});
+               } else {
+                       return $view->redirect("/$s->{id}/index");
+               }
        }
 
        # password mismatch
        my $view = new Keitairc::View($cf, $ci);
-       return $view->redirect("/");
+       return $view->redirect('/');
 }
 
 ################################################################
-sub action_404{
+sub action_error {
        my $request = shift;
+       my $error_code = shift;
        my $ci = new Keitairc::ClientInfo($request);
        my $view = new Keitairc::View($cf, $ci);
-       return $view->render('404.html', { action => $request->uri() });
+       return $view->render('error.html', { action => $request->uri(),
+                                            _http_status_code => $error_code,
+                                            _http_status_message => status_message($error_code) });
 }
 
 ################################################################
-# \82©\82ñ\82½\82ñ\83\8d\83O\83C\83\93\82ÌPOST\90æ
-# DoCoMo\82¾\82Á\82½\82çicc\82ª\97\88\82Ä\82¢\82é\82Í\82¸\82È\82Ì\82Å, icc + user_agent \82Å\83`\83F\83b\83N\81B
-# \8d\87\82Á\82Ä\82¢\82½\82ç\83Z\83b\83V\83\87\83\93\95\9c\8bA\82µ\82Ä /{SESSION}/index \82Ö
+sub action_public {
+       my $request = shift;
+       my $uri = shift;        # such as '/favicon.ico'
+       my $ci = new Keitairc::ClientInfo($request);
+       my $view = new Keitairc::View($cf, $ci);
+       return $view->public($request, $uri);
+}
+
+################################################################
+# かんたんログインのPOST先
+# DoCoMoだったらiccが来ているはずなので, icc + user_agent でチェック。
+# 合っていたらセッション復帰して /{SESSION}/index へ
 sub action_login_icc{
        my $request = shift;
        my $ci = new Keitairc::ClientInfo($request);
@@ -209,14 +260,14 @@ sub action_login_icc{
                if(length $docomo_foma_icc){
                        if(my $s = $sm->verify({serial_key => $docomo_foma_icc,
                                                user_agent => $ci->user_agent()})){
-                               ::log_debug("redirect to /$s->{id}/index from docomo_foma_icc");
+                               $log->log_debug("redirect to /$s->{id}/index from docomo_foma_icc");
                                my $view = new Keitairc::View($cf, $ci, $s->{id});
                                return $view->redirect("/$s->{id}/index");
                        }
 
                        if($docomo_foma_icc eq $cf->docomo_foma_icc()){
                                my $s = $sm->add($ci->user_agent(), $docomo_foma_icc);
-                               ::log_debug("redirect to /$s->{id}/index from docomo_foma_icc");
+                               $log->log_debug("redirect to /$s->{id}/index from docomo_foma_icc");
                                my $view = new Keitairc::View($cf, $ci, $s->{id});
                                return $view->redirect("/$s->{id}/index");
                        }
@@ -227,7 +278,46 @@ sub action_login_icc{
        }
 
        my $view = new Keitairc::View($cf, $ci);
-       return $view->render('root.html', { docomo => $ci->is_docomo() });
+       return $view->render('root.html', {
+               docomo_foma_icc => $cf->docomo_foma_icc(),
+               docomo_imodeid => $cf->docomo_imodeid(),
+                       });
+}
+
+################################################################
+# かんたんログインのPOST先
+# DoCoMoだったらiモードIDが来ているはずなので, iモードID + user_agent でチェック。
+# 合っていたらセッション復帰して /{SESSION}/index へ
+sub action_login_imodeid{
+       my $request = shift;
+       my $ci = new Keitairc::ClientInfo($request);
+       if($ci->is_docomo()){
+               my $docomo_imodeid = $ci->docomo_imodeid();
+               if(length $docomo_imodeid){
+                       if(my $s = $sm->verify({serial_key => $docomo_imodeid,
+                                               user_agent => $ci->user_agent()})){
+                               $log->log_debug("redirect to /$s->{id}/index from docomo_imodeid");
+                               my $view = new Keitairc::View($cf, $ci, $s->{id});
+                               return $view->redirect("/$s->{id}/index");
+                       }
+
+                       if($docomo_imodeid eq $cf->docomo_imodeid()){
+                               my $s = $sm->add($ci->user_agent(), $docomo_imodeid);
+                               $log->log_debug("redirect to /$s->{id}/index from docomo_imodeid");
+                               my $view = new Keitairc::View($cf, $ci, $s->{id});
+                               return $view->redirect("/$s->{id}/index");
+                       }
+
+                       my $view = new Keitairc::View($cf, $ci);
+                       return $view->render('login_imodeid.html', { imodeid => $docomo_imodeid });
+               }
+       }
+
+       my $view = new Keitairc::View($cf, $ci);
+       return $view->render('root.html', {
+               docomo_foma_icc => $cf->docomo_foma_icc(),
+               docomo_imodeid => $cf->docomo_imodeid(),
+                       });
 }
 
 ################################################################
@@ -237,29 +327,33 @@ sub action_root{
 
        if($ci->cookie_available()){
                my $session_id = $ci->{cookie}->{sid};
-               if(length $session_id){
+               if(defined($session_id) && length($session_id)){
                        if($sm->verify({session_id => $session_id,
                                        user_agent => $ci->user_agent()})){
-                               ::log_debug("redirect to /$session_id/index from cookie");
+                               $log->log_debug("redirect to /$session_id/index from cookie");
                                my $view = new Keitairc::View($cf, $ci, $session_id);
-                               return $view->redirect("/$session_id/index");
+                               if ($ci->is_webkit() && $cf->webkit_newui()) {
+                                       return add_cookie($view->render('root_home.html', {sid => $session_id}), $session_id);
+                               } else {
+                                       return $view->redirect("/$session_id/index");
+                               }
                        }
                }
        }
 
        if($ci->is_ezweb()){
-               my $subscriber_id = $ci->{header}->{x_up_subno};
+               my $subscriber_id = $ci->au_subscriber_id();
                if(length $subscriber_id){
                        if(my $s = $sm->verify({serial_key => $subscriber_id,
                                                user_agent => $ci->user_agent()})){
-                               ::log_debug("redirect to /$s->{id}/index from subscriber_id");
+                               $log->log_debug("redirect to /$s->{id}/index from subscriber_id");
                                my $view = new Keitairc::View($cf, $ci, $s->{id});
                                return $view->redirect("/$s->{id}/index");
                        }
 
                        if($subscriber_id eq $cf->au_subscriber_id()){
                                my $s = $sm->add($ci->user_agent(), $subscriber_id);
-                               ::log_debug("redirect to /$s->{id}/index from au_subscriber_id");
+                               $log->log_debug("redirect to /$s->{id}/index from au_subscriber_id");
                                my $view = new Keitairc::View($cf, $ci, $s->{id});
                                return $view->redirect("/$s->{id}/index");
                        }
@@ -271,7 +365,32 @@ sub action_root{
                if(length $serial_key){
                        if(my $s = $sm->verify({serial_key => $serial_key,
                                                user_agent => $ci->user_agent()})){
-                               ::log_debug("redirect to /$s->{id}/index from softbank serial_key");
+                               $log->log_debug("redirect to /$s->{id}/index from softbank serial_key");
+                               my $view = new Keitairc::View($cf, $ci, $s->{id});
+                               return $view->redirect("/$s->{id}/index");
+                       }
+                       if($serial_key eq $cf->softbank_serial_key()){
+                               my $s = $sm->add($ci->user_agent(), $serial_key);
+                               $log->log_debug("redirect to /$s->{id}/index from softbank_serial_key");
+                               my $view = new Keitairc::View($cf, $ci, $s->{id});
+                               return $view->redirect("/$s->{id}/index");
+                       }
+               }
+       }
+
+       if($ci->is_emobile()){
+               my $userid = $ci->emobile_userid();
+               if(length $userid){
+                       if(my $s = $sm->verify({serial_key => $userid,
+                                               user_agent => $ci->user_agent()})){
+                               $log->log_debug("redirect to /$s->{id}/index from userid");
+                               my $view = new Keitairc::View($cf, $ci, $s->{id});
+                               return $view->redirect("/$s->{id}/index");
+                       }
+
+                       if($userid eq $cf->emobile_userid()){
+                               my $s = $sm->add($ci->user_agent(), $userid);
+                               $log->log_debug("redirect to /$s->{id}/index from emobile_userid");
                                my $view = new Keitairc::View($cf, $ci, $s->{id});
                                return $view->redirect("/$s->{id}/index");
                        }
@@ -279,7 +398,10 @@ sub action_root{
        }
 
        my $view = new Keitairc::View($cf, $ci);
-       return $view->render('root.html', { docomo => $ci->is_docomo() });
+       return $view->render('root.html', {
+               docomo_foma_icc => $cf->docomo_foma_icc(),
+               docomo_imodeid => $cf->docomo_imodeid(),
+                       });
 }
 
 ################################################################
@@ -291,70 +413,91 @@ sub action_redirect_root{
 }
 
 ################################################################
-sub send_message{
+sub parse_message{
        my $request = shift;
-       my $channel = shift;
+       my $ci = new Keitairc::ClientInfo($request);
+       my $timestamp;
 
        my $message = $request->content();
-       $message =~ s/^m=//;
-       $message =~ s/\+/ /g;
-       $message = uri_unescape($message);
 
        if(length($message)){
-               my $jis = $message;
-               my $euc = $message;
-               Encode::from_to($jis, 'shiftjis', 'jis');
-               Encode::from_to($euc, 'shiftjis', 'euc-jp');
-               $irc->yield(privmsg => $channel => $jis);
-               $ib->add_message($channel, $euc, $cf->irc_nick());
-               $ib->message_added(1);
+               ($message, $timestamp) = split(/&/, $message);
+
+               $timestamp =~ s/^stamp=//g;
+
+               $message =~ s/^m=//;
+               $message =~ s/\+/ /g;
+               $message = uri_unescape($message);
+
+               if($ci->is_webkit() && !$cf->webkit_newui()){
+                       $message = fix_webkit_escape($message);
+               }
+       }
+       if ($cf->webkit_newui()) {
+               # ajax で投げ込んでるので utf8 できます
+               $message = Encode::decode('utf8', $message);
+       } else {
+               $message = Encode::decode($cf->web_charset(), $message);
        }
+       return ($message, $timestamp);
 }
 
-################################################################
-# \93ü\97Í\82Í euc-jp
-sub render_line{
-       local($_);
-       my $in = shift;
-       my $session_id = shift;
-       my $buf;
-
-       for ((reverse(split("\n", $in)))[0 .. $cf->web_lines()]){
-               next unless defined;
-               next unless length;
+sub send_message{
+       my $request = shift;
+       my $channel = shift;
 
-               $_ = $ib->simple_escape($_);
+       my ($message, $timestamp) = parse_message($request);
 
-               for my $name ($pl->list_replace_plugins()){
-                       last if s/$pl->{plugins}->{$name}->{message_replace_regexp}/$pl->{plugins}->{$name}->{message_replace_imprementation}($session_id, $1, $2, $3, $4, $5, $6, $7, $8, $9)/eg;
+       if(length($message) && length($channel)){
+               if($ib->update_timestamp($timestamp)){
+                       my $enc_message = Encode::encode($cf->irc_charset(), $message);
+                       my $enc_channel = Encode::encode($cf->irc_charset(), $channel);
+                       $irc->yield(privmsg => $enc_channel => $enc_message);
+                       my $cid = $ib->name2cid($channel);
+                       $ib->add_message($cid, $message, $cf->irc_nick());
                }
-
-               s/\s+$//;
-               s/\s+/ /g;
-               $buf .= "$_<br />";
        }
-
-       Encode::from_to($buf, 'euc-jp', 'shiftjis');
-       $buf;
 }
 
-################################################################
-sub log{
-       my $m = shift;
-       warn "keitairc: $m\n";
-       # TODO
-}
+sub send_command{
+       my $request = shift;
 
-sub log_die{
-       my $m = shift;
-       die "keitairc: $m\n";
-       # TODO
+       my ($message, $timestamp) = parse_message($request);
+
+       if(length($message)){
+               if($message =~ s|^/||) {
+                       my ($params, $trailing) = split(/ :/, $message, 2);
+                       my @postcmd = split(/ /, $params);
+                       push @postcmd, $trailing if defined $trailing;
+                       # This parser may be incomplete.
+                       if($postcmd[0] =~ /join/i) {
+                               if($postcmd[1] =~ /^\w/) {
+                                       $ib->join($postcmd[1]);
+                                       return;
+                               }
+                       } elsif($postcmd[0] =~ /part/i) {
+                               if($postcmd[1] =~ /^\w/) {
+                                       $ib->part($ib->name2cid($postcmd[1]));
+                                       return;
+                               }
+                       }
+                       $irc->yield(map { Encode::encode($cf->irc_charset(), $_) } @postcmd);
+               }
+       }
 }
 
-sub log_debug{
-       my $m = shift;
-       warn "keitairc: $m\n";
-       # TODO
+################################################################
+# posted string from Webkit browser
+# contains escaped utf-8 in the form %uXXXX
+# and may contains escaped Shift-JIS (web_charset) in the form \xXX
+# when operated from Safari/Mac OS X
+sub fix_webkit_escape{
+       # charset: $cf->irc_charset()
+       my $in = shift;
+       $in =~ s/\\x([0-9A-F]{2})/pack('C',hex($1))/egi;
+       #my $pi = Encode::decode('utf8', $in);
+       $in =~ s/%u([0-9A-F]{4})/pack('U',hex($1))/egi;
+       return $in;
 }
 
 __END__