[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