# -*- cperl -*- package POE::Component::IRC::Plugin::NoPaste::Httpd; use strict; use warnings; require Exporter; BEGIN { # with out this BEGIN block, NoPaste::Httpd::* can't import them by Exporter. our @ISA = qw(Exporter); our @EXPORT = qw( notfound_handler need_auth_handler redirect_handler internal_server_error_handler); } use POE qw( Component::Server::HTTP Component::IRC::Plugin::NoPaste::Template Component::IRC::Plugin::NoPaste::StaticFile Component::IRC::Plugin::NoPaste::Httpd::Index Component::IRC::Plugin::NoPaste::Httpd::Pasted ); use CGI::Pretty qw(:standard); use HTTP::Status; use Carp; use Hash::Util qw(lock_keys); sub spawn { my ($class, $nopaste) = @_; my $this = bless {} => $class; $this->{nopaste} = $nopaste; $this->{my_alias} = undef; $this->{httpd_alias} = undef; lock_keys %$this; POE::Session->create( heap => $this, package_states => [ __PACKAGE__, [ qw(_start shutdown) ], ], ); $this->{my_alias}; } sub _start { my $this = $_[HEAP]; $this->{my_alias} = 'PCI::Plugin::NoPaste::Httpd::' . $_[SESSION]->ID; $_[KERNEL]->alias_set($this->{my_alias}); my $barrier = sub { my $code = $_[0]; return sub { my ($req, $resp) = @_; local $ENV{OUTPUT_CHARSET} = 'UTF-8'; # copy the content of POST to query if needed. if ($req->method eq 'POST' and $req->headers->content_type eq 'application/x-www-form-urlencoded' and !defined $req->uri->query) { $_ = $req->content; tr/\x0d\x0a//d; $req->uri->query($_); } my $last_exception; my $retval = eval { local $SIG{__DIE__} = sub { print $_[0]; $last_exception = Carp::longmess(@_); }; $code->($this, @_); }; if ($@) { $retval = $this->internal_server_error_handler( $req, $resp, $last_exception); } # complete the response $resp->code or $resp->code($retval); $resp->message or $resp->message(status_message($resp->code)); if (!length $resp->content) { if ($resp->code >= 200 && $resp->code < 300) { $retval = $this->internal_server_error_handler( $req, $resp, 'Content handler did not set any content.'); } elsif ($resp->code >= 300 && $resp->code < 400) { # FIXME: complete a HTML which links to the destination of redirection } } # default value of Content-Type is text/html $resp->header('Content-Type') or $resp->header('Content-Type' => 'text/html; charset=UTF-8'); # complete Content-Length $resp->header('Content-Length') or $resp->header('Content-Length' => length($resp->content)); # remove content if the method is HEAD if ($req->method eq 'HEAD') { $resp->content(undef); } return $retval; }; }; $this->{httpd_alias} = POE::Component::Server::HTTP->new( Port => $this->{nopaste}{config}{httpd}{port}, ContentHandler => { '/' => $barrier->(\¬found_handler), '/css/' => $barrier->(\&css_handler), '/nopaste/' => $barrier->(\&POE::Component::IRC::Plugin::NoPaste::Httpd::Index::handler), '/post/' => $barrier->(\&POE::Component::IRC::Plugin::NoPaste::Httpd::Index::handler), '/pasted/' => $barrier->(\&POE::Component::IRC::Plugin::NoPaste::Httpd::Pasted::handler), }, Headers => { Server => 'PCI::Plugin::NoPaste UI (Powered by PoCo::Server::HTTP)', Connection => 'close', }, )->{httpd}; } sub shutdown { my $this = $_[HEAP]; $_[KERNEL]->alias_remove($this->{my_alias}); $_[KERNEL]->call($this->{httpd_alias} => 'shutdown'); } sub notfound_handler { my ($this, $req, $resp) = @_; my $t = POE::Component::IRC::Plugin::NoPaste::Template->new( -data => static_file('tmpl/error.html')); $t->expand( status_code => '404', status_message => 'Not Found', requested_url => ''.$req->uri, ); $resp->content($t->str); return RC_NOT_FOUND; } sub css_handler { my ($this, $req, $resp) = @_; (my $path = $req->uri->path) =~ s|^/css/||; my $content = static_file('css/'.$path); if (defined $content) { $resp->header('Content-Type' => 'text/css; charset=UTF-8'); $resp->content($content); return RC_OK; } else { return notfound_handler(@_); } } sub internal_server_error_handler { my ($this, $req, $resp, $error) = @_; my $style = { -style => 'white-space: pre;', }; my $html = join '', start_html( -title => 'Internal Server Error', -encoding => 'UTF-8', ), h1('Internal Server Error'), h2('Request'), p($style, $req->as_string), h2('Error'), p($style, $error), end_html; $resp->code(RC_INTERNAL_SERVER_ERROR); $resp->message(status_message($resp->code)); $resp->header('Content-Type' => 'text/html; charset=UTF-8'); $resp->content($html); return RC_INTERNAL_SERVER_ERROR; } sub redirect_handler { # $req : HTTP::Request # $resp: HTTP::Response # $path: destination of the redirection # $query_hash: query (can be undef) my ($this, $req, $resp, $path, $query_hash) = @_; $resp->header(Location => do { my $uri = $req->uri->clone; $uri->path($path); $uri->query(undef); $query_hash and $uri->query_form_hash($query_hash); $uri->fragment(undef); $uri->as_string; }); return RC_SEE_OTHER; } 1;