package HTTP::DAVServer::PROPFIND; our $VERSION=0.1; use strict; use warnings; =head1 NAME HTTP::DAVServer::PROPFIND - Implements the PROPFIND method =cut use File::stat qw(stat); our %fileProps = ( creationdate => 1, displayname => 1, getcontentlanguage => 1, getcontentlength => 1, getetag => 1, getlastmodified => 1, getcontenttype => 1, lockdiscovery => 1, resourcetype => 1, source => 1, supportedlock => 1, ); our %collProps = ( creationdate => 1, displayname => 1, resourcetype => 1, supportedlock => 1, ); sub handle { my ($self, $r, $url, $responder, $req) = @_; $responder->badRequest( $r, "PROPFINDVAGUE" ) unless exists $req->{'{DAV:}prop'} || exists $req->{'{DAV:}allprop'} || exists $req->{'{DAV:}propname'}; $url =~ s#/*$##; my $depth=$r->http('Depth'); $depth="infinite" unless $depth =~ /^(0|1|infinite)$/; my $root = $HTTP::DAVServer::ROOT; my $host = $HTTP::DAVServer::HOST; # Property names response if ( exists $req->{'{DAV:}propname'} ) { my $xml=""; if ( $depth eq "0") { $xml .= qq(\nhttp://$host$url\n\n); map { $xml .= qq(\t<$_/>\n) } keys %fileProps if -f "$root/$url"; map { $xml .= qq(\t<$_/>\n) } keys %collProps if -d "$root/$url"; if (-r "$root/$url") { $xml .= qq(\nHTTP/1.1 200 OK\n\n); } else { $xml .= qq(\nHTTP/1.1 404 Not Found\n\n); } } else { if ( -f "$root/$url" ) { $xml .= qq(\nhttp://$host$url\n\n); map { $xml .= qq(\t<$_/>\n) } keys %fileProps; $xml .= qq(\nHTTP/1.1 200 OK\n\n); } if ( -d "$root/$url" ) { $xml .= qq(\nhttp://$host$url\n\n); map { $xml .= qq(\t<$_/>\n) } keys %collProps; $xml .= qq(\nHTTP/1.1 200 OK\n\n); opendir DIR, "$root/$url"; while ( my $file = readdir DIR ) { if (-f "$root/$url/$file") { $xml .= qq(\n\n); $xml .= qq(\nhttp://$host$url/$file\n\n); map { $xml .= qq(\t<$_/>\n) } keys %fileProps; $xml .= qq(\nHTTP/1.1 200 OK\n\n); last; } } } } $xml .= "\n"; $responder->multiStatus( $r, $xml ); } my $reqProps=$req->{'{DAV:}prop'} || undef; # Property values response my $response=[]; fetchProps( $root, $url, "http://$host", $reqProps, $depth, $response ); my $xml = qq(\n) . join ( qq(\n\n), @$response) . qq(\n); $responder->multiStatus( $r, $xml ); } sub fetchProps { my ($root, $url, $urlPrefix, $reqProps, $depth, $response) = @_; $DB::single=1; my $path="$root$url"; if ($depth eq "0") { if (-d $path) { return push @$response, collection( $path, $url, $urlPrefix, $reqProps ); } else { return push @$response, file( $path, $url, $urlPrefix, $reqProps ); } } if ($depth eq "1") { if (-d $path) { fetchProps($root, $url, $urlPrefix, $reqProps, 0, $response); opendir PATH, $path; while (my $item = readdir PATH) { next if $item =~ /^\.\.?$/; fetchProps($root, "$url/$item", $urlPrefix, $reqProps, 0, $response); } closedir PATH; } else { fetchProps($root, $url, $urlPrefix, $reqProps, 0, $response); } } if ($depth eq "infinite") { if (-d $path) { opendir PATH, $path; while (my $item = readdir PATH) { fetchProps($root, "$url/$item", $urlPrefix, $reqProps, $depth, $response); } closedir PATH; } else { fetchProps($root, $url, $urlPrefix, $reqProps, 0, $response); } } } sub collection { my ($path, $url, $urlPrefix, $reqProps) = @_; $reqProps ||= \%collProps; my $stat=stat($path); my $ret=qq(\t$urlPrefix$url/\n) . qq(\t) . qq(\n); foreach my $reqProp ( keys %{$reqProps} ) { $reqProp =~ s/^{DAV:}//; $ret .= qq(\t<$reqProp>\n) and next if $reqProp eq "resourcetype"; if ($reqProp eq "creationdate") { $ret .= qq(\t<$reqProp>) . HTTP::DAVServer::dateEpoch( $stat->mtime ) . qq(\n); next; } if ($reqProp eq "displayname") { $path =~ /([^\/]+\/)$/; $ret .= qq(\t<$reqProp>$1\n); next; } warn "Collection property $reqProp not known\n" if $HTTP::DAVServer::WARN; } $ret .= qq() . qq(HTTP/1.1 200 OK) . qq(); return $ret; } sub file { my ($path, $url, $urlPrefix, $reqProps) = @_; $reqProps ||= \%fileProps; my $stat=stat($path); my $ret=qq(\t$urlPrefix$url\n) . qq(\t) . qq(\n); foreach my $reqProp ( keys %{$reqProps} ) { $reqProp =~ s/^{DAV:}//; $ret .= qq(\t<$reqProp/>\n) and next if $reqProp eq "resourcetype"; if ($reqProp eq "creationdate") { $ret .= qq(\t<$reqProp>) . HTTP::DAVServer::dateEpoch( $stat->mtime ) . qq(\n); next; } if ($reqProp eq "displayname") { $path =~ /([^\/]+\/)$/; $ret .= qq(\t<$reqProp>$1\n); next; } if ($reqProp eq "getcontentlength") { $ret .= qq(\t<$reqProp>) . $stat->size . qq(\n); next; } warn "Collection property $reqProp not known\n" if $HTTP::DAVServer::WARN; } $ret .= qq() . qq(HTTP/1.1 200 OK) . qq(); return $ret; } =head1 SUPPORT For technical support please email to jlawrenc@cpan.org ... for faster service please include "HTTP::DAVServer" and "help" in your subject line. =head1 AUTHOR Jay J. Lawrence - jlawrenc@cpan.org Infonium Inc., Canada http://www.infonium.ca/ =head1 COPYRIGHT Copyright (c) 2003 Jay J. Lawrence, Infonium Inc. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The full text of the license can be found in the LICENSE file included with this module. =head1 ACKNOWLEDGEMENTS Thank you to the authors of my prequisite modules. With out your help this code would be much more difficult to write! XML::Simple - Grant McLean XML::SAX - Matt Sergeant DateTime - Dave Rolsky Also the authors of litmus, a very helpful tool indeed! =head1 SEE ALSO HTTP::DAV, HTTP::Webdav, http://www.webdav.org/, RFC 2518 =cut 1;