📄 atomserver.pm
字号:
# Copyright 2001-2005 Six Apart.# SCRiPTMAFiA 2005 - THE DiRTY HANDS ON YOUR SCRiPTS## $Id: AtomServer.pm 10197 2005-03-09 00:27:57Z ezra $package MT::AtomServer;use strict;use XML::Atom;use XML::Atom::Util qw( first textValue );use base qw( MT::App );use MIME::Base64 ();use Digest::SHA1 ();use MT::Atom;use MT::Util qw( encode_xml );use MT::Author;use constant NS_SOAP => 'http://schemas.xmlsoap.org/soap/envelope/';use constant NS_WSSE => 'http://schemas.xmlsoap.org/ws/2002/07/secext';use constant NS_WSU => 'http://schemas.xmlsoap.org/ws/2002/07/utility';sub init { my $app = shift; $app->{no_read_body} = 1 if $app->request_method eq 'POST' || $app->request_method eq 'PUT'; $app->SUPER::init(@_) or return $app->error("Initialization failed"); $app->request_content if $app->request_method eq 'POST' || $app->request_method eq 'PUT'; $app->add_methods( handle => \&handle, ); $app->{default_mode} = 'handle'; $app->{is_admin} = 1; $app->{warning_trace} = 0; $app;}sub handle { my $app = shift; my $out = eval { (my $pi = $app->path_info) =~ s!^/!!; my($subapp, @args) = split /\//, $pi; $app->{param} = {}; for my $arg (@args) { my($k, $v) = split /=/, $arg, 2; $app->{param}{$k} = $v; } if (my $action = $app->get_header('SOAPAction')) { $app->{is_soap} = 1; $action =~ s/"//g; # " my($method) = $action =~ m!/([^/]+)$!; $app->request_method($method); } my $apps = $app->{cfg}->AtomApp; if (my $class = $apps->{$subapp}) { bless $app, $class; } my $out = $app->handle_request; return unless defined $out; if ($app->{is_soap}) { $out =~ s!^(<\?xml.*?\?>)!!; $out = <<SOAP;$1<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body>$out</soap:Body></soap:Envelope>SOAP } return $out; }; if ($@) { $app->error($@); $app->show_error("Internal Error"); } return $out;}sub handle_request { 1;}sub error { my $app = shift; my($code, $msg) = @_; return unless ref($app); $app->response_code($code); $app->response_message($msg); $app->SUPER::error($msg); return undef;}sub show_error { my $app = shift; my($err) = @_; chomp($err = encode_xml($err)); if ($app->{is_soap}) { my $code = $app->response_code; if ($code >= 400) { $app->response_code(500); $app->response_message($err); } return <<FAULT;<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <soap:Fault> <faultcode>$code</faultcode> <faultstring>$err</faultstring> </soap:Fault> </soap:Body></soap:Envelope>FAULT } else { return <<ERR;<error>$err</error>ERR }}sub get_auth_info { my $app = shift; my %param; if ($app->{is_soap}) { my $xml = $app->xml_body; my $auth = first($xml, NS_WSSE, 'UsernameToken'); $param{Username} = textValue($auth, NS_WSSE, 'Username'); $param{PasswordDigest} = textValue($auth, NS_WSSE, 'Password'); $param{Nonce} = textValue($auth, NS_WSSE, 'Nonce'); $param{Created} = textValue($auth, NS_WSU, 'Created'); } else { my $req = $app->get_header('X-WSSE') or return $app->auth_failure(401, 'X-WSSE authentication required'); $req =~ s/^WSSE //; my ($profile); ($profile, $req) = $req =~ /(\S+),?\s+(.*)/; return $app->error(400, "Unsupported WSSE authentication profile") if $profile !~ /\bUsernameToken\b/i; for my $i (split /,\s*/, $req) { my($k, $v) = split /=/, $i, 2; $v =~ s/^"//; $v =~ s/"$//; $param{$k} = $v; } } \%param;}use constant TIMEOUT_WINDOW => 120;sub authenticate { my $app = shift; my $auth = $app->get_auth_info or return $app->auth_failure(400, "No authentication info"); for my $f (qw( Username PasswordDigest Nonce Created )) { return $app->auth_failure(400, "X-WSSE requires $f") unless $auth->{$f}; } require MT::Session; my $nonce_record = MT::Session->load($auth->{Nonce}); if ($nonce_record && $nonce_record->id eq $auth->{Nonce}) { return $app->auth_failure(403, "Nonce already used"); } $nonce_record = new MT::Session(); $nonce_record->set_values({id => $auth->{Nonce}, created_on => time, kind => 'AN'}); $nonce_record->save();# xxx Expire sessions on shorter timeout? my $user = MT::Author->load({ name => $auth->{Username} }) or return $app->auth_failure(403, 'Invalid login'); my $created_on_epoch = $app->iso2epoch($auth->{Created}); if (abs(time - $created_on_epoch) > TIMEOUT_WINDOW) { return $app->auth_failure(403, 'X-WSSE UsernameToken timed out'); } $auth->{Nonce} = MIME::Base64::decode_base64($auth->{Nonce}); my $expected = Digest::SHA1::sha1_base64( $auth->{Nonce} . $auth->{Created} . $user->password); # Some base64 implementors do it wrong and don't put the = # padding on the end. This should protect us against that without # creating any holes. $expected =~ s/=*$//; $auth->{PasswordDigest} =~ s/=*$//; #print STDERR "expected $expected and got " . $auth->{PasswordDigest} . "\n"; return $app->auth_failure(403, 'X-WSSE PasswordDigest is incorrect') unless $expected eq $auth->{PasswordDigest}; $app->{user} = $user; return 1;}sub auth_failure { my $app = shift; $app->set_header('WWW-Authenticate', 'WSSE profile="UsernameToken"'); return $app->error(@_);}sub xml_body { my $app = shift; unless (exists $app->{xml_body}) { if (LIBXML) { my $parser = XML::LibXML->new; $app->{xml_body} = $parser->parse_string($app->request_content); } else { my $xp = XML::XPath->new(xml => $app->request_content); $app->{xml_body} = ($xp->find('/')->get_nodelist)[0]; } } $app->{xml_body};}sub atom_body { my $app = shift; my $atom; if ($app->{is_soap}) { my $xml = $app->xml_body; $atom = MT::Atom::Entry->new(Elem => first($xml, NS_SOAP, 'Body')) or return $app->error(500, MT::Atom::Entry->errstr); } else { $atom = MT::Atom::Entry->new(Stream => \$app->request_content) or return $app->error(500, MT::Atom::Entry->errstr); } $atom;}# $target_zone is expected to be a number of hours from GMTsub iso2ts { my $app = shift; my($ts, $target_zone) = @_; return unless $ts =~ /^(\d{4})(?:-?(\d{2})(?:-?(\d\d?)(?:T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|([+-]\d{2}:\d{2}))?)?)?)?/; my($y, $mo, $d, $h, $m, $s, $zone) = ($1, $2 || 1, $3 || 1, $4 || 0, $5 || 0, $6 || 0, $7); if ($zone) { my ($zh, $zm) = $zone =~ /([+-]\d\d):(\d\d)/; use Time::Local qw( timegm ); my $ts = timegm( $s, $m, $h, $d, $mo - 1, $y - 1900 ); if ($zone ne 'Z') { require MT::DateTime; my $tz_secs = MT::DateTime->tz_offset_as_seconds($zone); $ts -= $tz_secs; } if ($target_zone) { my $tz_secs = (3600 * int($target_zone) + 60 * abs($target_zone - int($target_zone))); $ts += $tz_secs; } else { } ($s, $m, $h, $d, $mo, $y) = gmtime( $ts ); $y += 1900; $mo++; } sprintf("%04d%02d%02d%02d%02d%02d", $y, $mo, $d, $h, $m, $s);}sub iso2epoch { my $app = shift; my($ts) = @_; return unless $ts =~ /^(\d{4})(?:-?(\d{2})(?:-?(\d\d?)(?:T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|([+-]\d{2}:\d{2}))?)?)?)?/; my($y, $mo, $d, $h, $m, $s, $zone) = ($1, $2 || 1, $3 || 1, $4 || 0, $5 || 0, $6 || 0, $7); use Time::Local; my $dt = timegm($s, $m, $h, $d, $mo-1, $y); if ($zone && $zone ne 'Z') { require MT::DateTime; my $tz_secs = MT::DateTime->tz_offset_as_seconds($zone); $dt -= $tz_secs; } $dt;}package MT::AtomServer::Weblog;use strict;use XML::Atom;use XML::Atom::Feed;use base qw( MT::AtomServer );use MT::Blog;use MT::Entry;use MT::Util qw( encode_xml );use MT::Permission;use File::Spec;use File::Basename;use constant NS_CATEGORY => 'http://sixapart.com/atom/category#';use constant NS_DC => 'http://purl.org/dc/elements/1.1/';use constant NS_PHOTOS => 'http://sixapart.com/atom/photo#';sub script { $_[0]->{cfg}->AtomScript . '/weblog' }sub handle_request {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -