=head1 NAME SuikaWiki::Output::HTTP --- SuikaWiki: HTTP or HTTP CGI output support =head1 DESCRIPTION This module provides HTTP or HTTP CGI outputing support. This module is part of SuikaWiki. =cut package SuikaWiki::Output::HTTP; use strict; our $VERSION = do{my @r=(q$Revision: 1.7 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r}; require IO::Handle; my %Status = ( 200 => q(OK), 201 => q(Created), 202 => q(Accepted), 203 => q(Non-Authoritative Information), 204 => q(No Content), 205 => q(Reset Content), 250 => q(Low on Storage Space), # RTSP 300 => q(Multiple Choices), 301 => q(Moved Permanently), 302 => q(Found), 303 => q(See Other), 304 => q(Not Modified), 307 => q(Moved Temporarily), 400 => q(Bad Request), 401 => q(Unauthorized), 403 => q(Forbidden), 404 => q(Not Found), 405 => q(Method Not Allowed), 406 => q(Not Acceptable), 408 => q(Request Timeout), 409 => q(Conflict), 410 => q(Gone), 413 => q(Request Entity Too Long), 414 => q(Request-URI Too Long), 415 => q(Unsupported Media Type), 423 => q(Locked), 480 => q(Temporariliy Not Available), # SIP 500 => q(Internal Server Error), 501 => q(Not Implemented), 503 => q(Service Unavailable), 505 => q(HTTP Version Not Supported), 507 => q(Insufficient Storage), ); my %Body = ( 301 => sub { my $self = shift; my $euri = $self->{redirect_uri}; $euri =~ s/&/&/g; $euri =~ s//>/g; $euri =~ s/"/"/g; qq(

See <$euri>

); }, ); $Body{302} = $Body{301}; $Body{303} = $Body{301}; $Body{307} = $Body{301}; =head1 METHODS =over 4 =item $http = SuikaWiki::Output::HTTP->new Constructs new instance of HTTP output implementation =cut sub new ($;%) { my $self = bless {header_field => [], negotiate_header_field => {Negotiate => 1}, entity => {language => [], media_type => 'application/octet-stream'}, }, shift; my %opt = @_; $self->{wiki} = ref $opt{wiki} ? $opt{wiki} : $opt{view}->{wiki}; $self->{view} = $opt{view}; $self->{viewobj} = $opt{viewobj}; $self->{-out_handle} = $opt{output_handle} || *STDOUT; $self; } sub add_header_field ($$$;%) { my ($self, $name => $body, %opt) = @_; push @{$self->{header_field}}, {name => $name, body => $body}; } sub add_negotiate_header_field ($$) { my ($self, $name) = @_; $self->{negotiate_header_field}->{$name} = 1; } sub set_expires ($%) { my ($self, %opt) = @_; if ($opt{time}) { ## TODO: use rfc1123-time push @{$self->{header_field}}, {name => 'Expires', body => scalar gmtime $opt{time}}; } elsif (defined $opt{delta}) { $self->{expires_delta} = $opt{delta} + 0; } } sub set_redirect ($%) { my ($self, %opt) = @_; $self->{redirect_uri} = $opt{uri}; if ($opt{status_code}) { $self->{status_code} = $opt{status_code}; } else { $self->{status_code} = 302; } $self->{status_phrase} = $opt{status_phrase}; } sub set_last_modified ($%) { my ($self, %opt) = @_; $self->{lastmodified_time} = $opt{time}; } sub append_to_body ($$) { my ($self, $s) = @_; $self->{entity}->{body} .= $s; } sub output ($;%) { my ($self, %opt) = @_; my $dg = $self->{wiki}->{var}->{client}->{downgrade}; ## Note: CGI/1.1 draft 3 recommends ("SHOULD") that ## "\x0A" should be used as new line in AmigaDOS and Un*x environments. my $nl = $opt{output} eq 'http-cgi' ? "\n" : "\x0D\x0A"; my $out = $opt{output_handle} || $self->{-out_handle}; binmode $out; $out->autoflush (1); my $status_code = $self->{status_code} || 200; my $status_phrase = $self->{status_phrase} || $Status{$status_code}; if ((300 < $status_code) && ($status_code < 400) && (defined $self->{redirect_uri})) { print $out "Location: $self->{redirect_uri}$nl"; $status_code = 302 if ($status_code == 303 or $status_code == 307) and ( ($self->{wiki}->{input} and $self->{wiki}->{input}->meta_variable ('SERVER_PROTOCOL') eq 'HTTP/1.0') or $dg->{http_no_see_other}); } if ($opt{output} eq 'http-cgi') { print $out "Status: $status_code $status_phrase$nl"; } else { print $out "$status_code $status_phrase HTTP/1.1$nl"; } for (@{$self->{header_field}}) { print $out "$_->{name}: $_->{body}$nl"; } my $time = time; print $out "Date: @{[scalar gmtime $time]}$nl"; if (defined $self->{expires_delta}) { print $out "Expires: @{[scalar gmtime ($time + $self->{expires_delta})]}$nl"; } if (defined $self->{lastmodified_time}) { print $out "Last-Modified: @{[scalar gmtime $self->{lastmodified_time}]}$nl"; } else { print $out "Last-Modified: @{[scalar gmtime $time]}$nl"; } print $out "Vary: ".join (', ', keys %{$self->{negotiate_header_field}})."$nl"; my $mt = $self->{entity}->{media_type} || 'application/octet-stream'; my $charset = $self->{entity}->{charset}; my $body = '' . $self->{entity}->{body}; if (not length $body and $self->{status_code} != 200) { $mt = q; $charset = 'iso-8859-1'; $body = ($Body{$self->{status_code}} or sub{})->($self); } ## TODO: $body = &main::code_convert (\$body, $self->{entity}->{charset}, $self->{wiki}->{config}->{charset}->{internal}) unless $self->{entity}->{body_is_octet_stream}; if (substr ($mt, -4) eq '+xml') { if ($mt eq 'application/rdf+xml' and $dg->{media_type_no_rdf_plus_xml}) { print $out "Content-Type: application/xml"; } elsif ($dg->{media_type_no_plus_xml}) { print $out "Content-Type: application/xml"; } elsif ($dg->{media_type_no_xml}) { print $out "Content-Type: text/plain"; } else { print $out "Content-Type: $mt"; } } else { print $out "Content-Type: $mt"; } if ($charset) { if ($dg->{media_type_no_parameter}) { } elsif ($dg->{charset_name_with_x}) { print $out qq(; charset="@{[{qw/euc-jp x-euc-jp shift_jis x-sjis/} ->{$charset} or $charset]}"); } else { print $out qq(; charset=).$self->___quote_word ($charset); } } print $nl; for (join ', ', @{$self->{entity}->{language}||[]}) { print $out "Content-Language: $_$nl" if $_; } print $out "Content-Length: @{[length $body]}$nl"; print $out $nl; print $out $body; } ## Note: CGI/1.1 draft 3 does not allow HTTP Header Fields ## when Location: field is outputed. This module does ## NOT follow that requirement. The draft allows ## message-body explicitly, even if there is Location:. ## Therefore at least entity-header fields should be allowed. ## TODO: REDIRECT_URI support =item $self->exit Declares the instance ($self) no longer considered useful. =cut sub exit ($) { my $self = shift; delete $self->{wiki}; delete $self->{view}; delete $self->{viewobj}; $self->{exited} = 1; } sub DESTROY ($) { my $self = shift; $self->exit unless $self->{exited}; } sub ___quote_word ($$) { my ($self, $s) = @_; if ($s =~ /[^0-9A-Za-z_.+-]/) { $s =~ s/([\\"])/\\$1/g; return qq<"$s">; } else { return $s; } } =item $self->{entity}->{charset} =item $self->{entity}->{language} = [...] =item $self->{entity}->{media_type} =back =head1 TODO Use manakai. =head1 LICENSE Copyright 2003 Wakaba This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut 1; # $Date: 2004/03/19 11:24:17 $