[Rt-commit] [svn] r811 - in RTx-Atom: . html html/Atom html/Atom/0.3 html/Atom/0.3/Add html/Atom/0.3/Auth html/Atom/0.3/Describe html/Atom/0.3/Elements html/Atom/0.3/Get html/Atom/0.3/NoAuth html/Atom/0.3/Put html/Atom/0.3/Remove html/Atom/0.3/Search html/Atom/0.3/Update

autrijus at pallas.eruditorum.org autrijus at pallas.eruditorum.org
Sun May 2 21:03:09 EDT 2004


Author: autrijus
Date: Sun May  2 21:03:09 2004
New Revision: 811

Added:
   RTx-Atom/Makefile.PL
   RTx-Atom/html/
   RTx-Atom/html/Atom/
   RTx-Atom/html/Atom/0.3/
   RTx-Atom/html/Atom/0.3/Add/
   RTx-Atom/html/Atom/0.3/Add/index
   RTx-Atom/html/Atom/0.3/Auth/
   RTx-Atom/html/Atom/0.3/Describe/
   RTx-Atom/html/Atom/0.3/Describe/index
   RTx-Atom/html/Atom/0.3/Elements/
   RTx-Atom/html/Atom/0.3/Elements/Error
   RTx-Atom/html/Atom/0.3/Elements/Introspect
   RTx-Atom/html/Atom/0.3/Elements/Link
   RTx-Atom/html/Atom/0.3/Get/
   RTx-Atom/html/Atom/0.3/Get/index
   RTx-Atom/html/Atom/0.3/NoAuth/
   RTx-Atom/html/Atom/0.3/NoAuth/feed.css
   RTx-Atom/html/Atom/0.3/NoAuth/index.css
   RTx-Atom/html/Atom/0.3/Put/
   RTx-Atom/html/Atom/0.3/Put/index
   RTx-Atom/html/Atom/0.3/Remove/
   RTx-Atom/html/Atom/0.3/Remove/index
   RTx-Atom/html/Atom/0.3/Search/
   RTx-Atom/html/Atom/0.3/Search/index
   RTx-Atom/html/Atom/0.3/Update/
   RTx-Atom/html/Atom/0.3/Update/index
   RTx-Atom/html/Atom/0.3/autohandler
   RTx-Atom/html/Atom/0.3/dhandler
   RTx-Atom/html/Atom/0.3/index
Modified:
   RTx-Atom/   (props changed)
Log:
 ----------------------------------------------------------------------
 r4396 at not:  autrijus | 2004-05-03T01:03:07.876995Z
 
 * Initial commit of Atom/0.3 API for RT.
 ----------------------------------------------------------------------


Added: RTx-Atom/Makefile.PL
==============================================================================
--- (empty file)
+++ RTx-Atom/Makefile.PL	Sun May  2 21:03:09 2004
@@ -0,0 +1,13 @@
+#!/usr/bin/env perl
+# $File: //depot/RT/osf/Makefile.PL $ $Author: autrijus $
+# $Revision: #7 $ $Change: 9904 $ $DateTime: 2004/02/04 19:02:17 $
+
+use inc::Module::Install;
+
+RTx('Atom');
+author('Autrijus Tang <autrijus at autrijus.org>');
+abstract('Atom API for RT');
+license('gpl');
+
+&WriteAll( check_nmake => 0, sign => 1 );
+

Added: RTx-Atom/html/Atom/0.3/Add/index
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/Add/index	Sun May  2 21:03:09 2004
@@ -0,0 +1,6 @@
+%# [POST PostURI] (Container)
+%# Create a new object from the AtomEntry in the request's body.
+%# 303: Created.  The 'Location:' header is set to the new object's EditURI
+%#      (for subsequent Get/Update).  Body is success message in text/plain.
+%# 400: Request failed.  Body is error message in text/plain.
+%# 404: There is no container matching the specified URI.

Added: RTx-Atom/html/Atom/0.3/Describe/index
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/Describe/index	Sun May  2 21:03:09 2004
@@ -0,0 +1,6 @@
+%# [OPTIONS PostURI], [GET PostURI]
+%# On a container, returns the schema of objects acceptable by this container.
+%# On an object, returns the schema acceptable by the specified 'adverb'.
+%# 200: Success.  Body is schema in a format determined by content negotiation.
+%# 400: Request failed.  Body is error message in text/plain.
+%# 404: There is no container matching the specified URI.

Added: RTx-Atom/html/Atom/0.3/Elements/Error
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/Elements/Error	Sun May  2 21:03:09 2004
@@ -0,0 +1,8 @@
+<%INIT>
+$r->content_type('text/html');
+$r->status($Status);
+$m->abort($Status) unless $ENV{FCGI_ROLE};
+</%INIT>
+<%ARGS>
+$Status => 500
+</%ARGS>

Added: RTx-Atom/html/Atom/0.3/Elements/Introspect
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/Elements/Introspect	Sun May  2 21:03:09 2004
@@ -0,0 +1,48 @@
+<%INIT>
+my %collection_to_obj;
+my %collection_to_class;
+my %record_to_collection;
+foreach my $key (keys %INC) {
+    $key =~ m{^(RTx?(?:/.+)?/([^_/]+)).pm$}i or next;
+    my ($class, $type) = ($1, $2);
+    $class =~ s{/}{::}g;
+    $class->can('NewItem') or next;
+    my $obj = eval {$class->new($session{CurrentUser})->NewItem} or next;
+    $collection_to_obj{$type} = $obj;
+    $collection_to_class{$type} = $class;
+    $record_to_collection{$1} = $type if ref($obj) =~ /(\w+)$/;
+}
+
+my @collection = sort keys %collection_to_obj;
+my @record = (
+    map {($_, $_."Id")} (qw(Member PrincipalType Object),
+    sort keys %record_to_collection)
+);
+
+my %gone;
+foreach my $type (@collection) {
+    my $obj = $collection_to_obj{$type} or next;
+
+    # First, eliminate collections that can be inferred from other objects
+    foreach my $method (@collection) {
+	$gone{$method}++ if $obj->can($method) or $obj->can("_$method");
+    }
+
+    # Next, eliminate normalization records with multiple parent fields 
+    my $score = 0;
+    foreach my $property (@record) {
+	$score++ if $obj->_Accessible($property, 'read');
+    }
+    $gone{$type}++ if $score > 1;
+}
+
+delete @collection_to_class{keys %gone};
+return \%collection_to_class if $Want eq 'CollectionToClass';
+return \%record_to_collection if $Want eq 'RecordToCollection';
+
+delete @collection_to_obj{keys %gone};
+return [sort keys %collection_to_obj];
+</%INIT>
+<%ARGS>
+$Want
+</%ARGS>

Added: RTx-Atom/html/Atom/0.3/Elements/Link
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/Elements/Link	Sun May  2 21:03:09 2004
@@ -0,0 +1,31 @@
+%# Make a HTML link and an Atom link
+<link rel="<% $Relation %>" type="<% $Type %>" href="<% $URI %>" title="<% $Title %>" />
+<a accesskey="<% $accesskey %>" class="<% $class %>" rel="<% $Relation %>" type="<% $Type %>" href="<% $URI %>" title="<% $Title %>" xmlns="http://www.w3.org/1999/xhtml"><% loc($TextMap{$Relation}) || $Title %></a>
+<%INIT>
+my %TextMap = (
+    'alternate'	    => 'HTML',	# loc
+    'service.post'  => 'Create',# loc
+    'service.feed'  => 'Index',	# loc
+    'service.edit'  => 'Edit',	# loc
+);
+my $class = lc($TextMap{$Relation} || 'nav');
+my %KeyMap = (
+    'alternate'	    => 'h',
+    'next'	    => 'n',
+    'prev'	    => 'p',
+    'service.post'  => 'c',
+    'service.feed'  => 'i',
+);
+my $accesskey = $KeyMap{$Relation};
+if ($IsEntry) {
+    $accesskey = ($m->notes('EntryAccessKey') + 1) % 10;
+    $m->notes(EntryAccessKey => $accesskey);
+}
+</%INIT>
+<%ARGS>
+$Relation => "alternate"
+$Type => "application/x.atom+xml"
+$URI => "#"
+$Title => ""
+$IsEntry => 0
+</%ARGS>

Added: RTx-Atom/html/Atom/0.3/Get/index
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/Get/index	Sun May  2 21:03:09 2004
@@ -0,0 +1,5 @@
+%# [GET EditURI]
+%# Get a representation of an object.
+%# 200: Success.  Body is the object, serialized as an AtomEntry.
+%# 400: Request failed.  Body is error message in text/plain.
+%# 404: There is no object matching the specified URI.

Added: RTx-Atom/html/Atom/0.3/NoAuth/feed.css
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/NoAuth/feed.css	Sun May  2 21:03:09 2004
@@ -0,0 +1,120 @@
+feed {
+  display:block;
+  text-align:center;
+  font-family:verdana, sans-serif;
+  margin:2%;
+}
+
+info {
+  margin:10px 0px 10px 0px;
+  background:#003366;
+  display:block;
+  padding:3px;
+  font-size:85%;
+}
+
+title {
+  font-size:150%;
+  color:#000000;
+  display:block;
+  text-align:left;
+  font-weight:bold;
+}
+
+tagline, author {
+  text-align:left;
+  display:block;
+}
+
+a {
+  text-decoration: none;
+}
+
+a:hover {
+  text-decoration: underline;
+}
+
+a {
+  padding: 1px 5px 1px 5px;
+  font-size: xx-small;
+  color: #000000;
+}
+
+a.nav {
+  font-size:80%;
+  padding: 5px;
+  border:solid 1px #dddddd;
+  font-size: 90%;
+  color: #0000ff;
+}
+
+a.html {
+  background-color: #003366;
+  color: #ccccff;
+}
+
+a.create {
+  background-color: #66ee66;
+  font-weight: bold;
+}
+
+a.index {
+  background-color: #cccccc;
+}
+
+a.edit {
+  background-color: #cccc66;
+  color: #000033;
+  font-weight: bold;
+}
+
+entry title {
+  font-size:125%;
+  font-weight:bold;
+  color:#003366;
+  display:block;
+  padding:5px 0px 0px 0px;
+  margin:0px 0px 0px 0px;
+  border-top:solid 1px #dddddd;
+  background:#eeeeee;
+}
+
+id, modified, created, generator, issued, url {
+  display:none;
+}
+
+entry modified {
+  display:inline;
+  font-style: italic;
+}
+
+content {
+  background:#eeeeee;
+  display:block;
+  padding:5px 3% 10px 3%;
+  border-bottom:solid 1px #999999;
+  font-family: courier;
+  font-size: xx-small;
+}
+
+
+
+entry {
+  text-align:left;
+  border-top:dotted 1px #999999;
+  display:block;
+  background:#ffffff;
+  padding:5px 0px 0px 0px;
+  margin:5px 5px 0px 10px;
+  
+}
+
+issued,modified,created,name,id {
+  color:#999999;
+  font-size:80%;
+  margin-top:25px;
+}
+
+name {
+  font-weight: bold;
+}

Added: RTx-Atom/html/Atom/0.3/NoAuth/index.css
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/NoAuth/index.css	Sun May  2 21:03:09 2004
@@ -0,0 +1,23 @@
+ at import url(feed.css);
+
+feed {
+  display:block;
+  text-align:left;
+}
+
+link[rel="service.feed"] {
+  text-align:left;
+  border-top:dotted 1px #999999;
+  display:block;
+  background:#ffffff;
+  padding:5px 0px 0px 0px;
+  margin:5px 5px 0px 10px;
+}
+
+link[rel="service.feed"]:before {
+  content: attr(title);
+  font-size:150%;
+  color:#000000;
+  text-align:left;
+  font-weight:bold;
+}

Added: RTx-Atom/html/Atom/0.3/Put/index
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/Put/index	Sun May  2 21:03:09 2004
@@ -0,0 +1,5 @@
+%# [PUT EditURI]
+%# Modifies an object with the AtomEntry in the request body.
+%# 200: Success.  Body is the object, serialized as an AtomEntry.
+%# 400: Request failed.  Body is error message in text/plain.
+%# 404: There is no object matching the specified URI.

Added: RTx-Atom/html/Atom/0.3/Remove/index
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/Remove/index	Sun May  2 21:03:09 2004
@@ -0,0 +1,5 @@
+%# [DELETE EditURI]
+%# Delete an object.
+%# 200: Successfully deleted.  Body is success message in text/plain.
+%# 400: Request failed.  Body is error message in text/plain.
+%# 404: There is no object matching the specified URI.

Added: RTx-Atom/html/Atom/0.3/Search/index
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/Search/index	Sun May  2 21:03:09 2004
@@ -0,0 +1,107 @@
+%# [GET FeedURI]
+%# Search for objects within an container.
+%# Possible query parameters: rows, page, query.
+%# 200: Success.  Body is the result, serialized as an AtomFeed.
+%# 400: Request failed.  Body is error message in text/plain.
+%# 404: There is no container matching the specified URI.
+<?xml version="1.0" encoding="utf-8"?>
+<?xml-stylesheet type="text/css" href="<% $BaseURI %>/NoAuth/feed.css"?>
+<feed version="0.3" xmlns="http://purl.org/atom/ns#">
+  <title><&|/l&>Query</&>: <% loc($Type) %></title>
+  <author>
+    <name><% $RT::Organization %></name>
+    <url><% $RT::WebURL %></url>
+  </author>
+  <tagline mode="escaped">
+    <&|/l, $page, int(($TotalFound-1)/$rows)+1&>Page [_1] of [_2]</&>
+    (<&|/l, $TotalFound&>[_1] Total</&>)
+  </tagline>
+  <& $Link, Relation => "service.feed", URI => $BaseURI, Title => loc("Homepage") &>
+  <& $Link, Relation => "service.post", URI => "$FeedURI!add", Title => loc("Create"). ": ". loc($Type) &>
+%# XXX - The URI below is incorrect; should point to collection URL
+  <& $Link, Type => 'text/html', URI => $RT::WebURL, Title => loc($Type) &>
+  <modified><% $Now->W3CDTF %></modified>
+  <generator url="http://www.bestpractical.com/rt/" version="<% $RT::VERSION %>">RT</generator>
+% foreach my $entry (@entries) {
+  <entry>
+    <title mode="escaped"><% $entry->{Name} || "#$entry->{Id}" %></title>
+    <& $Link, Relation => "service.edit", URI => "$FeedURI/$entry->{Id}", Title => loc("Edit"). ": $entry->{Name}", IsEntry => 1 &>
+    <summary mode="escaped"><% $entry->{Description} %></summary>
+    <modified><% $entry->{LastUpdated} %></modified>
+    <issued><% $entry->{Created} %></issued>
+    <created><% $entry->{Created} %></created>
+    <id><% $entry->{URI} %></id>
+% if ($entry->{HTML_URL}) {
+    <& $Link, Type => 'text/html', URI => "$RT::WebURL$entry->{HTML_URL}", Title => $entry->{Name} &>
+% }
+  </entry>
+% }
+  <info></info>
+% if ($page > 1) {
+  <& $Link, URI => "$FeedURI?$prev", Title => loc("Previous Page"), Relation => 'prev', &>
+% }
+% if (($page * $rows) < $TotalFound) {
+  <& $Link, URI => "$FeedURI?$next", Title => loc("Next Page"), Relation => 'next', &>
+% }
+</feed>
+<%INIT>
+my %URI = (
+    Tickets	=> 'Ticket/Display.html?id=',
+    Templates	=> 'Admin/Global/Template.html?Template=',
+    Scrips	=> 'Admin/Global/Scrip.html?id=',
+    Queues	=> 'Admin/Queues/Modify.html?id=',
+    Users	=> 'Admin/Users/Modify.html?id=',
+    Groups	=> 'Admin/Groups/Modify.html?id=',
+);
+
+my $List = $CollectionClass->new($session{CurrentUser});
+$List->UnLimit;
+$List->RowsPerPage($rows) if $rows > 0;
+$List->GotoPage($page - 1) if $page > 0;
+
+my $TotalFound = $List->CountAll;
+$rows = $TotalFound if $rows <= 0;
+$page = 1 if $page <= 0;
+
+my @entries;
+
+while (my $entry = $List->Next) {
+    my %entry = map { $_ => eval { $entry->$_ } || '' }
+	qw(Id Name Description URI);
+    $entry{Created} = eval { $entry->CreatedObj->W3CDTF }
+	|| eval { $entry->PrincipalObj->CreatedObj->W3CDTF };
+    $entry{LastUpdated} = eval { $entry->LastUpdatedObj->W3CDTF }
+	|| eval { $entry->PrincipalObj->LastUpdatedObj->W3CDTF };
+
+    if ($URI{$Type}) {
+	$entry{HTML_URI} = $URI{$Type} . $entry{Id};
+	if (my $queue = eval { $entry->Queue } ) {
+	    $entry{HTML_URI} =~ s/Global/Queues/;
+	    $entry{Params} .= "&Queue=$queue";
+	}
+    }
+    push @entries, \%entry;
+}
+
+my %query;
+while (my ($k, $v) = each %ARGS) {
+    $query{$k} = $v if $k eq lc($k);
+}
+my $prev = $m->comp('/Elements/QueryString', %query, page => ($page-1));
+my $next = $m->comp('/Elements/QueryString', %query, page => ($page+1));
+
+</%INIT>
+<%ARGS>
+$Path
+$BaseURI
+$Link
+$Now
+
+$Type
+$CollectionClass
+$FeedURI
+
+$rows => 10
+$page => 1
+$query => undef
+</%ARGS>

Added: RTx-Atom/html/Atom/0.3/Update/index
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/Update/index	Sun May  2 21:03:09 2004
@@ -0,0 +1,5 @@
+%# [POST PostURI] (Object)
+%# Updates an object, using an 'adverb' acceptable to that object's class.
+%# 200: Success.  Body is the object, serialized as an AtomEntry.
+%# 400: Request failed.  Body is error message in text/plain.
+%# 404: The specific object does not exist, or does not support this adverb.

Added: RTx-Atom/html/Atom/0.3/autohandler
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/autohandler	Sun May  2 21:03:09 2004
@@ -0,0 +1,15 @@
+%# Forbid direct access in this directory -- everything goes thru dhandler
+% # If it's a noauth file, don't ask for auth.
+% my $path = $m->base_comp->path;
+% if ($path =~ $RT::WebNoAuthRegex ) {
+%     $r->content_type('text/css') if $path =~ /\.css$/i;
+%     $r->content_type('text/xml') if $path =~ /\.xsl$/i;
+%     $r->content_type('image/png') if $path =~ /\.png$/i;
+%     $m->call_next(%ARGS);
+%     $m->abort();
+% }
+% $r->content_type('text/html; charset=utf-8');
+% $m->abort(403);
+<%flags>
+inherit	=> undef
+</%flags>

Added: RTx-Atom/html/Atom/0.3/dhandler
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/dhandler	Sun May  2 21:03:09 2004
@@ -0,0 +1,224 @@
+%# The main dispatcher for RT/REST 2.0
+<%INIT>
+require Digest::MD5;
+require MIME::Base64;
+
+# needs discussion on using MD5(pass) as Digest token
+ at RT::RESTAuthenticationMethods = qw( WSSE Basic )
+    unless @RT::RESTAuthenticationMethods;
+
+my $realm = $RT::rtname;
+$realm =~ s/[^\w.]//g;
+my $nonce = Digest::MD5::md5_hex($realm . rand());
+my %methods = map {($_ => 1)} @RT::RESTAuthenticationMethods;
+
+my %accept = map { $_ => 1 } $r->header_in('Accept') =~ m{([^\s,]+/[^;,]+)}g;
+my $atom_client = $accept{'application/x.atom+xml'};
+
+my $header_out = sub {
+    $ENV{FCGI_ROLE} ? $r->header_out(@_) : $r->headers_out->add(@_);
+};
+
+$header_out->(
+    'WWW-Authenticate' => qq(WSSE realm="$realm", profile="UsernameToken")
+) if $methods{WSSE} and $atom_client;
+$header_out->(
+    'WWW-Authenticate' => qq(Digest realm="$realm", stale=false, nonce="", qop="auth", algorithm="MD5")
+) if $methods{Digest} and !$atom_client;
+$header_out->(
+    'WWW-Authenticate' => qq(Basic realm="$realm")
+) if $methods{Basic} and !$atom_client;
+
+my $CurrentUser;
+my $headerParts = sub {
+    my $header = $r->header_in($_[0]) || $ENV{$_[0]};
+    $header =~ s/^(?:$_[1]) /", / or return;
+    $header =~ s/"\s*$//; # strip whitespaces after the last "
+
+    my %parts;
+    foreach my $chunk (split /,\s*/, $header) {
+	my ($k, $v) = split /=/, $chunk, 2;
+	$v =~ s/^"//;
+	$v =~ s/"$//;
+	$parts{lc($k)} = $v;
+    }
+    $parts{lc($_)} = delete $parts{$_} for map "$_", keys %parts;
+    return \%parts;
+};
+
+AUTH_Basic: {
+    last if $CurrentUser or !$methods{Basic};
+
+    ($r->header_in('Authorization') || $ENV{'Authorization'})
+         =~ /^Basic (.+)$/ or last;
+    my ($username, $password) = split(/:/, MIME::Base64::decode_base64($1), 2);
+
+    require RT::CurrentUser;
+    $CurrentUser = RT::CurrentUser->new;
+    $CurrentUser->Load($username) or last;
+    $CurrentUser->IsPassword($password) or undef $CurrentUser;
+}
+
+AUTH_Digest: {
+    last if $CurrentUser or !$methods{Digest};
+
+    my $parts = $headerParts->('Authorization', 'Digest') or last;
+
+    my ($username, $auth_digest, $auth_nonce,
+	$auth_nc, $auth_cnonce, $auth_qop, $auth_uri)
+	= map { defined($_) ? $_ : last AUTH_Digest }
+	    @{$parts}{qw(username response nonce nc cnonce qop uri)};
+
+    # XXX validate $auth_uri
+
+    require RT::CurrentUser;
+    $CurrentUser = RT::CurrentUser->new;
+    $CurrentUser->Load($username) or last;
+
+    my $a1 = Digest::MD5::md5_hex(
+	"$username:$realm:" . $CurrentUser->UserObj->__Value('Password')
+    );
+
+    $auth_digest eq Digest::MD5::md5_hex(
+	join(
+	    ":", 
+	    $a1, $auth_nonce,
+	    $auth_nc, $auth_cnonce, $auth_qop,
+	    Digest::MD5::md5_hex($r->method . ":" . $auth_uri),
+	)
+    ) or undef $CurrentUser;
+}
+
+AUTH_WSSE: {
+    last if $CurrentUser or !$methods{WSSE};
+    my $wsse = $headerParts->('X-WSSE', qr/WSSE|UsernameToken/) or last;
+
+    my ($username, $auth_digest, $auth_nonce, $auth_created)
+	= map { defined($_) ? $_ : last AUTH_WSSE }
+	    @{$wsse}{qw(username passworddigest nonce created)};
+
+    require RT::CurrentUser;
+    $CurrentUser = RT::CurrentUser->new;
+    $CurrentUser->Load($username) or last;
+
+    # check against reused nonces
+    require MIME::Base64;
+    $auth_nonce = MIME::Base64::decode_base64($auth_nonce);
+
+    my $nonce_cache;
+    require Cache::FileCache;
+    $nonce_cache = Cache::FileCache->new({
+	namespace => 'RT-Nonces',
+	default_expires_in => 1728000,
+	auto_purge_interval => 3600,
+    });
+    $auth_nonce = substr($auth_nonce, 0, 32);
+    (undef($CurrentUser), last) if $nonce_cache->get( $auth_nonce );
+
+    # if ($auth_created and abs($auth_created - time) >= 864000) {
+    #	last; # system clock differ by more than one day, oops!
+    # }
+
+    $CurrentUser->Authenticate(
+	$auth_digest, $auth_created, $auth_nonce, $realm
+    ) or (undef($CurrentUser), last);
+
+    # remember issued nonces
+    $nonce_cache->set( $auth_nonce, 1 );
+}
+
+if (!$CurrentUser or !$CurrentUser->Id) {
+    return $m->comp('Elements/Error', Status => 401);
+}
+
+$session{CurrentUser} = $CurrentUser;
+
+my $verb = {
+    GET	    => 'Get',
+    HEAD    => 'Get',
+    POST    => 'Add',
+    PUT	    => 'Put',
+    DELETE  => 'Remove',
+    OPTIONS => 'Describe',
+}->{$r->method} or return $m->comp('Elements/Error', Status => 405);
+
+my $path = $m->dhandler_arg;
+my ($type, @parts) = grep length, split('/', $path);
+$type =~ s/-/::/g;
+
+my $adverb = '';
+if ($type =~ s/!(\w+)$//) {
+    $adverb = $1;
+    $verb = 'Describe' if $verb eq 'Get';
+    $m->comp('Elements/Error', Status => 405)
+        unless $verb =~ /Add|Describe/;
+}
+
+if ((@parts % 2) == 0) {
+    # FeedURI on collection
+    $verb = 'Search' if $verb eq 'Get';
+}
+elsif ($adverb) {
+    # PostURI on object
+    $verb = 'Update' if $verb eq 'Add';
+}
+
+my $map = $m->comp('Elements/Introspect', Want => 'CollectionToClass');
+my $class;
+foreach my $key (keys %$map) {
+    $key =~ /\b\Q$type\E$/ or next;
+    $class = $map->{$key};
+    $type = $1 if $class =~ m/([^:]+)$/;
+    last;
+}
+
+if (!$class) {
+    $map = $m->comp('Elements/Introspect', Want => 'RecordToCollection');
+
+    foreach my $key (keys %$map) {
+	$key =~ /\b\Q$type\E$/ or next;
+	my $new_type = $map->{$key};
+	$new_type =~ s/::/-/g;
+	$path =~ s{([^/]*)}{$new_type};
+	$r->header_out(Location => $path);
+	return $m->comp('Elements/Error', Status => 301);
+    }
+}
+
+my $BaseURI = "$RT::WebPath/REST/2.0";
+$ARGS{Path} = $path;
+$ARGS{BaseURI} = $BaseURI;
+$ARGS{Link} = "$BaseURI/Elements/Link";
+$ARGS{Now} = RT::Date->new($session{CurrentUser});
+$ARGS{Now}->SetToNow;
+
+my @types = qw(
+    application/x.atom+xml
+    application/xhtml+xml
+    application/xml
+);
+my $content_type = 'text/xml'; # fallback
+foreach my $try_type (@types) {
+    $accept{$try_type} or next;
+    $content_type = $try_type;
+    last;
+}
+
+$r->content_type("$content_type; charset=utf-8");
+
+if (!$class) {
+    return $m->comp('index', %ARGS) if $path =~ /index|^\W*$/i;
+    return $m->comp('Elements/Error', Status => 404);
+}
+
+$m->comp(
+    "$verb/index", %ARGS,
+    Type => $type,
+    Adverb => $adverb,
+    CollectionClass => $class,
+    FeedURI => "$BaseURI/\L$type",
+);
+</%INIT>
+<%FLAGS>
+inherit	=> undef
+</%FLAGS>

Added: RTx-Atom/html/Atom/0.3/index
==============================================================================
--- (empty file)
+++ RTx-Atom/html/Atom/0.3/index	Sun May  2 21:03:09 2004
@@ -0,0 +1,23 @@
+%# Put service links to all actions and objects here
+<?xml version="1.0" encoding="utf-8"?>
+<?xml-stylesheet type="text/css" href="<% $BaseURI %>/NoAuth/index.css"?>
+<feed version="0.3" xmlns="http://purl.org/atom/ns#">
+  <title><&|/l&>Homepage</&></title>
+  <author>
+    <name><% $RT::Organization %></name>
+    <url><% $RT::WebURL %></url>
+  </author>
+  <& $Link, Type => 'text/html', URI => $RT::WebURL, Title => loc("Homepage") &>
+% foreach my $type (@{$m->comp('Elements/Introspect', Want => 'TopLevelCollections')}) {
+  <& $Link, Relation => 'service.feed', URI => "$BaseURI/\L$type", Title => loc($type), IsEntry => 1 &>
+  <& $Link, Relation => 'service.post', URI => "$BaseURI/\L$type!add", Title => loc("Create") . ": " . loc($type) &>
+% }
+  <modified><% $Now->W3CDTF %></modified>
+  <generator url="http://www.bestpractical.com/rt/" version="<% $RT::VERSION %>">RT</generator>
+</feed>
+<%ARGS>
+$Path
+$BaseURI
+$Link
+$Now
+</%ARGS>


More information about the Rt-commit mailing list