/[pub]/suikawiki/script/lib/SuikaWiki/Output/HTTP.pm
Suika

Contents of /suikawiki/script/lib/SuikaWiki/Output/HTTP.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.4 - (show annotations) (download)
Fri Dec 26 06:48:09 2003 UTC (22 years, 6 months ago) by wakaba
Branch: MAIN
Changes since 1.3: +23 -3 lines
(output): Don't quote charset parameter value of Content-Type header field body, since legacy browser (such as WinIE 6) does not support quoted-string there

1
2 =head1 NAME
3
4 SuikaWiki::Output::HTTP --- SuikaWiki: HTTP or HTTP CGI output support
5
6 =head1 DESCRIPTION
7
8 This module provides HTTP or HTTP CGI outputing support.
9
10 This module is part of SuikaWiki.
11
12 =cut
13
14 package SuikaWiki::Output::HTTP;
15 use strict;
16 our $VERSION = do{my @r=(q$Revision: 1.3 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
17
18 my %Status = (
19 200 => q(OK),
20 201 => q(Created),
21 202 => q(Accepted),
22 203 => q(Non-Authoritative Information),
23 204 => q(No Content),
24 205 => q(Reset Content),
25 250 => q(Low on Storage Space), # RTSP
26 300 => q(Multiple Choices),
27 301 => q(Moved Permanently),
28 302 => q(Found),
29 303 => q(See Other),
30 304 => q(Not Modified),
31 307 => q(Moved Temporarily),
32 400 => q(Bad Request),
33 401 => q(Unauthorized),
34 403 => q(Forbidden),
35 404 => q(Not Found),
36 405 => q(Method Not Allowed),
37 406 => q(Not Acceptable),
38 408 => q(Request Timeout),
39 409 => q(Conflict),
40 410 => q(Gone),
41 413 => q(Request Entity Too Long),
42 414 => q(Request-URI Too Long),
43 415 => q(Unsupported Media Type),
44 423 => q(Locked),
45 480 => q(Temporariliy Not Available), # SIP
46 500 => q(Internal Server Error),
47 501 => q(Not Implemented),
48 503 => q(Service Unavailable),
49 505 => q(HTTP Version Not Supported),
50 507 => q(Insufficient Storage),
51 );
52 my %Body = (
53 301 => sub {
54 my $self = shift;
55 my $euri = $self->{redirect_uri};
56 $euri =~ s/&/&/g;
57 $euri =~ s/</&lt;/g;
58 $euri =~ s/>/&gt;/g;
59 $euri =~ s/"/&quot;/g;
60 qq(<!DOCTYPE p SYSTEM><p>See &lt;<a href="$euri">$euri</a>&gt;</p>);
61 },
62 );
63 $Body{302} = $Body{301};
64 $Body{303} = $Body{301};
65 $Body{307} = $Body{301};
66
67 =head1 METHODS
68
69 =over 4
70
71 =item $http = SuikaWiki::Output::HTTP->new
72
73 Constructs new instance of HTTP output implementation
74
75 =cut
76
77 sub new ($;%) {
78 my $self = bless {header_field => [],
79 negotiate_header_field => {Negotiate => 1},
80 entity => {language => [],
81 media_type => 'application/octet-stream'},
82 }, shift;
83 my %opt = @_;
84 $self->{wiki} = ref $opt{wiki} ? $opt{wiki} : $opt{view}->{wiki};
85 $self->{view} = $opt{view};
86 $self->{viewobj} = $opt{viewobj};
87 $self;
88 }
89
90 sub add_header_field ($$$;%) {
91 my ($self, $name => $body, %opt) = @_;
92 push @{$self->{header_field}}, {name => $name, body => $body};
93 }
94
95 sub add_negotiate_header_field ($$) {
96 my ($self, $name) = @_;
97 $self->{negotiate_header_field}->{$name} = 1;
98 }
99
100 sub set_expires ($%) {
101 my ($self, %opt) = @_;
102 if ($opt{time}) {
103 ## TODO: use rfc1123-time
104 push @{$self->{header_field}}, {name => 'Expires',
105 body => scalar gmtime $opt{time}};
106 } elsif (defined $opt{delta}) {
107 $self->{expires_delta} = $opt{delta} + 0;
108 }
109 }
110
111 sub set_redirect ($%) {
112 my ($self, %opt) = @_;
113 $self->{redirect_uri} = $opt{uri};
114 if ($opt{status_code}) {
115 $self->{status_code} = $opt{status_code};
116 } else {
117 $self->{status_code} = 302;
118 }
119 $self->{status_phrase} = $opt{status_phrase};
120 }
121
122 sub set_last_modified ($%) {
123 my ($self, %opt) = @_;
124 $self->{lastmodified_time} = $opt{time};
125 }
126
127 sub append_to_body ($$) {
128 my ($self, $s) = @_;
129 $self->{entity}->{body} .= $s;
130 }
131
132 sub output ($;%) {
133 my ($self, %opt) = @_;
134 ## Note: CGI/1.1 draft 3 recommends ("SHOULD") that
135 ## "\x0A" should be used as new line in AmigaDOS and Un*x environments.
136 my $nl = $opt{output} eq 'http-cgi' ? "\n" : "\x0D\x0A";
137 binmode STDOUT;
138 $| = 1;
139
140 my $status_code = $self->{status_code} || 200;
141 my $status_phrase = $self->{status_phrase} || $Status{$status_code};
142 if ((300 < $status_code) && ($status_code < 400)
143 && (defined $self->{redirect_uri})) {
144 print "Location: $self->{redirect_uri}$nl";
145 $status_code = 302
146 if ($status_code == 303 || $status_code == 307)
147 && (($main::ENV{SERVER_PROTOCOL} eq 'HTTP/1.0') ## TODO: use common interface
148 || ($self->{wiki}->{var}->{client}->{user_agent_name}
149 =~ m#M(?:ozilla/[0-4]\.|icrosoft Internet Explorer)#));
150 }
151 if ($opt{output} eq 'http-cgi') {
152 print "Status: $status_code $status_phrase$nl";
153 } else {
154 print "$status_code $status_phrase HTTP/1.1$nl";
155 }
156
157 for (@{$self->{header_field}}) {
158 print "$_->{name}: $_->{body}$nl";
159 }
160
161 my $time = time;
162 print "Date: @{[scalar gmtime $time]}$nl";
163 if (defined $self->{expires_delta}) {
164 print "Expires: @{[scalar gmtime ($time + $self->{expires_delta})]}$nl";
165 }
166 if (defined $self->{lastmodified_time}) {
167 print "Last-Modified: @{[scalar gmtime $self->{lastmodified_time}]}$nl";
168 } else {
169 print "Last-Modified: @{[scalar gmtime $time]}$nl";
170 }
171 print "Vary: ".join (', ', keys %{$self->{negotiate_header_field}})."$nl";
172
173 my $mt = $self->{entity}->{media_type} || 'application/octet-stream';
174 my $charset = $self->{entity}->{charset};
175 my $body = '' . $self->{entity}->{body};
176 unless (length $body) {
177 $mt = q<text/html>;
178 $charset = 'iso-8859-1';
179 $body = &{$Body{$self->{status_code}||200}||sub{}} ($self);
180 }
181 ## TODO:
182 $body = &main::code_convert (\$body, $self->{entity}->{charset});
183
184 if (($mt eq 'application/rdf+xml')
185 && (index ($self->{wiki}->{var}->{client}->{user_agent_name},
186 'Gecko') > -1)) {
187 print "Content-Type: application/xml";
188 } else {
189 print "Content-Type: $mt";
190 }
191 if ($charset) {
192 if ($self->{wiki}->{var}->{client}->{user_agent_name}
193 =~ m#Mozilla/[12]\.#) {
194 print qq(; charset="@{[{qw/euc-jp x-euc-jp shift_jis x-sjis/}
195 ->{$charset}
196 || $charset]}");
197 } elsif ($self->{wiki}->{var}->{client}->{user_agent_name}
198 =~ m#Mozilla/0\.|Infomosaic#) {
199 } else {
200 print qq(; charset=).$self->___quote_word ($charset);
201 }
202 }
203 print $nl;
204
205 for (join ', ', @{$self->{entity}->{language}||[]}) {
206 print "Content-Language: $_$nl" if $_;
207 }
208 print "Content-Length: @{[length $body]}$nl";
209 print $nl;
210 print $body;
211 }
212
213 ## Note: CGI/1.1 draft 3 does not allow HTTP Header Fields
214 ## when Location: field is outputed. This module does
215 ## NOT follow that requirement. The draft allows
216 ## message-body explicitly, even if there is Location:.
217 ## Therefore at least entity-header fields should be allowed.
218
219 ## TODO: REDIRECT_URI support
220
221 =item $self->exit
222
223 Declares the instance ($self) no longer considered useful.
224
225 =cut
226
227 sub exit ($) {
228 my $self = shift;
229 delete $self->{wiki};
230 delete $self->{view};
231 delete $self->{viewobj};
232 $self->{exited} = 1;
233 }
234
235 sub DESTROY ($) {
236 my $self = shift;
237 $self->exit unless $self->{exited};
238 }
239
240 sub ___quote_word ($$) {
241 my ($self, $s) = @_;
242 if ($s =~ /[^0-9A-Za-z_.+-]/) {
243 $s =~ s/([\\"])/\\$1/g;
244 return qq<"$s">;
245 } else {
246 return $s;
247 }
248 }
249
250 =item $self->{entity}->{charset}
251 =item $self->{entity}->{language} = [...]
252 =item $self->{entity}->{media_type}
253
254 =back
255
256 =head1 TODO
257
258 Use manakai.
259
260 =head1 LICENSE
261
262 Copyright 2003 Wakaba <w@suika.fam.cx>
263
264 This program is free software; you can redistribute it and/or
265 modify it under the same terms as Perl itself.
266
267 =cut
268
269 1; # $Date: 2003/12/06 05:38:13 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24