=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; $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