File Coverage

blib/lib/Net/DAVTalk.pm
Criterion Covered Total %
statement 39 227 17.1
branch 0 92 0.0
condition 0 37 0.0
subroutine 13 30 43.3
pod 17 17 100.0
total 69 403 17.1


line stmt bran cond sub pod time code
1             package Net::DAVTalk;
2              
3 2     2   487213 use strict;
  2         5  
  2         93  
4              
5 2     2   11 use Carp;
  2         4  
  2         161  
6 2     2   1817 use DateTime::Format::ISO8601;
  2         1846532  
  2         145  
7 2     2   21 use DateTime::TimeZone;
  2         3  
  2         56  
8 2     2   2125 use HTTP::Tiny;
  2         115338  
  2         102  
9 2     2   1807 use JSON;
  2         24816  
  2         19  
10 2     2   1375 use Tie::DataUUID qw{$uuid};
  2         7031  
  2         18  
11 2     2   1469 use XML::Spice;
  2         3506  
  2         18  
12 2     2   1209 use Net::DAVTalk::XMLParser;
  2         8  
  2         277  
13 2     2   1221 use MIME::Base64 qw(encode_base64);
  2         2003  
  2         153  
14 2     2   18 use Encode qw(encode);
  2         11  
  2         164  
15 2     2   1220 use URI::Escape qw(uri_escape uri_unescape);
  2         4273  
  2         145  
16 2     2   1494 use URI;
  2         8276  
  2         7358  
17              
18             =head1 NAME
19              
20             Net::DAVTalk - Interface to talk to DAV servers
21              
22             =head1 VERSION
23              
24             Version 0.24
25              
26             =cut
27              
28             our $VERSION = '0.24';
29              
30             =head1 SYNOPSIS
31              
32             Net::DAVTalk is was originally designed as a service module for Net::CalDAVTalk
33             and Net::CardDAVTalk, abstracting the process of connecting to a DAV server and
34             parsing the XML responses.
35              
36             Example:
37              
38             use Net::DAVTalk;
39             use XML::Spice;
40              
41             my $davtalk = Net::DAVTalk->new(
42             url => "https://dav.example.com/",
43             user => "foo\@example.com",
44             password => "letmein",
45             headers => { Cookie => "123", Referer => "456" },
46             );
47              
48             $davtalk->Request(
49             'MKCALENDAR',
50             "$calendarId/",
51             x('C:mkcalendar', $Self->NS(),
52             x('D:set',
53             x('D:prop', @Properties),
54             ),
55             ),
56             );
57              
58             $davtalk->Request(
59             'DELETE',
60             "$calendarId/",
61             );
62              
63             =head1 SUBROUTINES/METHODS
64              
65             =head2 $class->new(%Options)
66              
67             Options:
68              
69             url: either full https?:// url, or relative base path on the
70             server to the DAV endpoint
71              
72             host, scheme and port: alternative to using full URL.
73             If URL doesn't start with https?:// then these will be used to
74             construct the endpoint URI.
75              
76             expandurl and wellknown: if these are set, then the wellknown
77             name (caldav and carddav are both defined) will be used to
78             resolve /.well-known/$wellknown to find the current-user-principal
79             URI, and then THAT will be resovlved to find the $wellknown-home-set
80             URI, which will be used as the URL for all further actions on
81             this object.
82              
83             user and password: if these are set, perform basic authentication.
84             user and access_token: if these are set, perform Bearer (OAUTH2)
85             authentication.
86              
87             headers: a hashref of additional headers to add to every request
88              
89             SSL_options: a hashref of SSL options to pass down to the default
90             user agent
91             =cut
92              
93             # General methods
94              
95             sub new {
96 0     0 1   my ($Class, %Params) = @_;
97              
98 0 0         unless ($Params{url}) {
99 0           confess "URL not supplied";
100             }
101              
102             # Assume url points to xyz-home-set, otherwise expand the url
103 0 0         if (delete $Params{expandurl}) {
104             # Locating Services for CalDAV and CardDAV (RFC6764)
105 0           my $PrincipalURL = $Class->GetCurrentUserPrincipal(%Params);
106 0           $Params{principal} = $PrincipalURL;
107              
108 0           my $HomeSet = $Class->GetHomeSet(
109             %Params,
110             url => $PrincipalURL,
111             );
112              
113 0           $Params{url} = $HomeSet;
114             }
115              
116 0   0       my $Self = bless \%Params, ref($Class) || $Class;
117 0           $Self->SetURL($Params{url});
118 0           $Self->SetPrincipalURL($Params{principal});
119 0           $Self->ns(D => 'DAV:');
120              
121 0           return $Self;
122             }
123              
124             =head2 my $ua = $Self->ua();
125             =head2 $Self->ua($setua);
126              
127             Get or set the useragent (HTTP::Tiny or compatible) that will be used to make
128             the requests:
129              
130             e.g.
131              
132             my $ua = $Self->ua();
133              
134             $Self->ua(HTTP::Tiny->new(agent => "MyAgent/1.0", timeout => 5));
135              
136             =cut
137              
138             sub ua {
139 0     0 1   my $Self = shift;
140 0 0         if (@_) {
141 0           $Self->{ua} = shift;
142             }
143             else {
144             $Self->{ua} ||= HTTP::Tiny->new(
145             agent => "Net-DAVTalk/$VERSION",
146             SSL_options => $Self->{SSL_options},
147 0   0       );
148             }
149 0           return $Self->{ua};
150             }
151              
152             =head2 $Self->SetURL($url)
153              
154             Change the endpoint URL for an existing connection.
155              
156             =cut
157              
158             sub SetURL {
159 0     0 1   my ($Self, $URL) = @_;
160              
161 0           $URL =~ s{/$}{}; # remove any trailing slash
162              
163 0 0         if ($URL =~ m{^https?://}) {
164 0           my ($HTTPS, $Hostname, $Port, $BasePath)
165             = $URL =~ m{^http(s)?://([^/:]+)(?::(\d+))?(.*)?};
166              
167 0 0         unless ($Hostname) {
168 0           confess "Invalid hostname in '$URL'";
169             }
170              
171 0 0         $Self->{scheme} = $HTTPS ? 'https' : 'http';
172 0           $Self->{host} = $Hostname;
173 0   0       $Self->{port} = ($Port || ($HTTPS ? 443 : 80));
174 0           $Self->{basepath} = $BasePath;
175             }
176             else {
177 0           $Self->{basepath} = $URL;
178             }
179              
180 0           $Self->{url} = "$Self->{scheme}://$Self->{host}:$Self->{port}$Self->{basepath}";
181              
182 0           return $Self->{url};
183             }
184              
185             =head2 $Self->SetPrincipalURL($url)
186              
187             Set the URL to the DAV Principal
188              
189             =cut
190              
191             sub SetPrincipalURL {
192 0     0 1   my ($Self, $PrincipalURL) = @_;
193              
194 0           return $Self->{principal} = $PrincipalURL;
195             }
196              
197             =head2 $Self->fullpath($shortpath)
198              
199             Convert from a relative path to a full path:
200              
201             e.g
202             my $path = $Dav->fullpath('Default');
203             ## /dav/calendars/user/foo/Default
204              
205             NOTE: a you can pass a non-relative full path (leading /)
206             to this function and it will be returned unchanged.
207              
208             =cut
209              
210             sub fullpath {
211 0     0 1   my $Self = shift;
212 0           my $path = shift;
213 0           my $basepath = $Self->{basepath};
214 0 0         return $path if $path =~ m{^/};
215 0           return "$basepath/$path";
216             }
217              
218             =head2 $Self->shortpath($fullpath)
219              
220             Convert from a full path to a relative path
221              
222             e.g
223             my $path = $Dav->fullpath('/dav/calendars/user/foo/Default');
224             ## Default
225              
226             NOTE: if the full path is outside the basepath of the object, it
227             will be unchanged.
228              
229             my $path = $Dav->fullpath('/dav/calendars/user/bar/Default');
230             ## /dav/calendars/user/bar/Default
231              
232             =cut
233              
234             sub shortpath {
235 0     0 1   my $Self = shift;
236 0           my $origpath = shift;
237 0           my $basepath = $Self->{basepath};
238 0           my $path = $origpath;
239 0           $path =~ s{^$basepath/?}{};
240 0 0         return ($path eq '' ? $origpath : $path);
241             }
242              
243             =head2 $Self->Request($method, $path, $content, %headers)
244              
245             The whole point of the module! Perform a DAV request against the
246             endpoint, returning the response as a parsed hash.
247              
248             method: http method, i.e. GET, PROPFIND, MKCOL, DELETE, etc
249              
250             path: relative to base url. With a leading slash, relative to
251             server root, i.e. "Default/", "/dav/calendars/user/foo/Default".
252              
253             content: if the method takes a body, raw bytes to send
254              
255             headers: additional headers to add to request, i.e (Depth => 1)
256              
257             In the event of timeout or most HTTP errors, an exception will be raised.
258              
259             =head2 $Self->SafeRequest($method, $path, $content, %headers)
260              
261             my $Result = $DAVTalk->SafeRequest($method, $path, $content, %headers)
262              
263             This is mostly Request, but it won't throw an exception on most errors.
264              
265             Instead, it always returns a reference to a hash with these entries:
266              
267             payload - a structured representation of the DAV response, if possible;
268             this is the same thing that the ->Request method returns
269             http_response - the underlying HTTP response from HTTP::Tiny
270             error - a string describing the error condition, if any
271              
272             The C<Request> method uses C<SafeRequest> under the hood, converting error
273             results into exceptions.
274              
275             =cut
276              
277             sub SafeRequest {
278 0     0 1   my ($Self, $Method, $Path, $Content, %Headers) = @_;
279              
280             # setup request {{{
281              
282 0 0         $Content = '' unless defined $Content;
283 0           my $Bytes = encode('UTF-8', $Content);
284              
285 0           my $ua = $Self->ua();
286              
287 0   0       $Headers{'Content-Type'} //= 'application/xml; charset=utf-8';
288              
289 0 0         if ($Self->{user}) {
290 0           $Headers{'Authorization'} = $Self->auth_header();
291             }
292              
293 0 0         if ($Self->{headers}) {
294 0           $Headers{$_} = $Self->{headers}->{$_} for ( keys %{ $Self->{headers} } );
  0            
295             }
296              
297             # XXX - Accept-Encoding for gzip, etc?
298              
299             # }}}
300              
301             # send request {{{
302              
303 0           my $URI = $Self->request_url($Path);
304              
305 0           my $Response = $ua->request($Method, $URI, {
306             headers => \%Headers,
307             content => $Bytes,
308             });
309              
310 0 0 0       if ($Response->{status} == '599' and $Response->{content} =~ m/timed out/i) {
311             return {
312 0           http_response => $Response,
313             error => "Error with $Method for $URI (504, Gateway Timeout)",
314             };
315             }
316              
317 0           my $count = 0;
318 0   0       while ($Response->{status} =~ m{^30[1278]} and (++$count < 10)) {
319 0           my $location = URI->new_abs($Response->{headers}{location}, $URI);
320 0 0         if ($ENV{DEBUGDAV}) {
321 0           warn "******** REDIRECT ($count) $Response->{status} to $location\n";
322             }
323              
324 0           $Response = $ua->request($Method, $location, {
325             headers => \%Headers,
326             content => $Bytes,
327             });
328              
329 0 0 0       if ($Response->{status} == '599' and $Response->{content} =~ m/timed out/i) {
330             return {
331 0           http_response => $Response,
332             error => "Error with $Method for $location (504, Gateway Timeout)",
333             };
334             }
335             }
336              
337             # one is enough
338              
339 0   0       my $ResponseContent = $Response->{content} || '';
340              
341 0 0         if ($ENV{DEBUGDAV}) {
342 0           warn "<<<<<<<< $Method $URI HTTP/1.1\n$Bytes\n" .
343             ">>>>>>>> $Response->{protocol} $Response->{status} $Response->{reason}\n$ResponseContent\n" .
344             "========\n\n";
345             }
346              
347 0 0 0       if ($Method eq 'REPORT' && $Response->{status} == 403) {
348             # maybe invalid sync token, need to return that fact
349 0           my $Xml = xmlToHash($ResponseContent);
350 0 0         if (exists $Xml->{"{DAV:}valid-sync-token"}) {
351             # This weird "return error in payload" behavior is legacy behavior, and
352             # quite possibly should be changed. It's the only place we returned an
353             # "error" in the hashref from Request before writing SafeRequest.
354             # -- rjbs, 2026-02-11
355             return {
356 0           http_response => $Response,
357             payload => { error => "valid-sync-token" },
358             };
359             }
360             }
361              
362 0 0         unless ($Response->{success}) {
363             return {
364 0           http_response => $Response,
365             error => "ERROR WITH REQUEST\n"
366             . "<<<<<<<< $Method $URI HTTP/1.1\n$Bytes\n"
367             . ">>>>>>>> $Response->{protocol} $Response->{status} $Response->{reason}\n$ResponseContent\n"
368             . "========\n\n",
369             };
370             }
371              
372 0 0 0       if ((grep { $Method eq $_ } qw{GET DELETE}) or ($Response->{status} != 207) or (not $ResponseContent)) {
  0   0        
373             return {
374 0           http_response => $Response,
375             payload => { content => $ResponseContent },
376             };
377             }
378              
379             # }}}
380              
381             # parse XML response {{{
382 0           my $Xml = xmlToHash($ResponseContent);
383              
384             # Normalise XML
385              
386 0 0         if (exists($Xml->{"{DAV:}response"})) {
387 0 0         if (ref($Xml->{"{DAV:}response"}) ne 'ARRAY') {
388 0           $Xml->{"{DAV:}response"} = [ $Xml->{"{DAV:}response"} ];
389             }
390              
391 0           foreach my $Response (@{$Xml->{"{DAV:}response"}}) {
  0            
392 0 0         if (exists($Response->{"{DAV:}propstat"})) {
393 0 0         unless (ref($Response->{"{DAV:}propstat"}) eq 'ARRAY') {
394 0           $Response->{"{DAV:}propstat"} = [$Response->{"{DAV:}propstat"}];
395             }
396             }
397             }
398             }
399              
400             return {
401 0           http_response => $Response,
402             payload => $Xml,
403             };
404              
405             # }}}
406             }
407              
408             sub Request {
409 0     0 1   my ($Self, @Rest) = @_;
410 0           my ($Struct) = $Self->SafeRequest(@Rest);
411              
412 0 0         if ($Struct->{error}) {
413 0           confess $Struct->{error};
414             }
415              
416 0           return $Struct->{payload};
417             }
418              
419             =head2 $Self->GetProps($Path, @Props)
420              
421             perform a propfind on a particular path and get the properties back
422              
423             =cut
424              
425             sub GetProps {
426 0     0 1   my ($Self, $Path, @Props) = @_;
427 0           my @res = $Self->GetPropsArray($Path, @Props);
428 0 0         return wantarray ? map { $_->[0] } @res : $res[0][0];
  0            
429             }
430              
431             =head2 $Self->GetPropsArray($Path, @Props)
432              
433             perform a propfind on a particular path and get the properties back
434             as an array of one or more items
435              
436             =cut
437              
438             sub GetPropsArray {
439 0     0 1   my ($Self, $Path, @Props) = @_;
440              
441             # Fetch one or more properties.
442             # Use [ 'prop', 'sub', 'item' ] to dig into result structure
443              
444 0           my $NS_D = $Self->ns('D');
445              
446             my $Response = $Self->Request(
447             'PROPFIND',
448             $Path,
449             x('D:propfind', $Self->NS(),
450             x('D:prop',
451 0 0         map { ref $_ ? x($_->[0]): x($_) } @Props,
  0            
452             ),
453             ),
454             Depth => 0,
455             );
456              
457 0           my @Results;
458 0 0         foreach my $Response (@{$Response->{"{$NS_D}response"} || []}) {
  0            
459 0 0         foreach my $Propstat (@{$Response->{"{$NS_D}propstat"} || []}) {
  0            
460 0   0       my $PropData = $Propstat->{"{$NS_D}prop"} || next;
461 0           for my $Prop (@Props) {
462 0           my @Values = ($PropData);
463              
464             # Array ref means we need to git through structure
465 0 0         foreach my $Key (ref $Prop ? @$Prop : $Prop) {
466 0           my @New;
467 0           foreach my $Result (@Values) {
468 0 0         if ($Key =~ m/:/) {
469 0           my ($N, $P) = split /:/, $Key;
470 0           my $NS = $Self->ns($N);
471 0           $Result = $Result->{"{$NS}$P"};
472             } else {
473 0           $Result = $Result->{$Key};
474             }
475 0 0         if (ref($Result) eq 'ARRAY') {
    0          
476 0           push @New, @$Result;
477             }
478             elsif (defined $Result) {
479 0           push @New, $Result;
480             }
481             }
482 0           @Values = @New;
483             }
484              
485 0           push @Results, [ map { $_->{content} } @Values ];
  0            
486             }
487             }
488             }
489              
490 0 0         return wantarray ? @Results : $Results[0];
491             }
492              
493             =head2 $Self->GetCurrentUserPrincipal()
494             =head2 $class->GetCurrentUserPrincipal(%Args)
495              
496             Can be called with the same args as new() as a class method, or
497             on an existing object. Either way it will use the .well-known
498             URI to find the path to the current-user-principal.
499              
500             Returns a string with the path.
501              
502             =cut
503              
504             sub GetCurrentUserPrincipal {
505 0     0 1   my ($Class, %Args) = @_;
506              
507 0 0         if (ref $Class) {
508 0           %Args = %{$Class};
  0            
509 0           $Class = ref $Class;
510             }
511              
512 0   0       my $OriginalURL = $Args{url} || '';
513 0           my $Self = $Class->new(%Args);
514 0           my $NS_D = $Self->ns('D');
515 0           my @BasePath = split '/', $Self->{basepath};
516              
517 0 0         @BasePath = ('', ".well-known/$Args{wellknown}") unless @BasePath;
518              
519 0           PRINCIPAL: while(1) {
520 0           $Self->SetURL(join '/', @BasePath);
521              
522 0 0         if (my $Principal = $Self->GetProps('', [ 'D:current-user-principal', 'D:href' ])) {
523 0           $Self->SetURL(uri_unescape($Principal));
524 0           return $Self->{url};
525             }
526              
527 0           pop @BasePath;
528 0 0         last unless @BasePath;
529             }
530              
531 0           croak "Error finding current user principal at '$OriginalURL'";
532             }
533              
534             =head2 $Self->GetHomeSet
535             =head2 $class->GetHomeSet(%Args)
536              
537             Can be called with the same args as new() as a class method, or
538             on an existing object. Either way it assumes that the created
539             object has a 'url' parameter pointing at the current user principal
540             URL (see GetCurrentUserPrincipal above)
541              
542             Returns a string with the path to the home set.
543              
544             =cut
545              
546             sub GetHomeSet {
547 0     0 1   my ($Class, %Args) = @_;
548              
549 0 0         if (ref $Class) {
550 0           %Args = %{$Class};
  0            
551 0           $Class = ref $Class;
552             }
553              
554 0   0       my $OriginalURL = $Args{url} || '';
555 0           my $Self = $Class->new(%Args);
556 0           my $NS_D = $Self->ns('D');
557 0           my $NS_HS = $Self->ns($Args{homesetns});
558 0           my $HomeSet = $Args{homeset};
559              
560 0 0         if (my $Homeset = $Self->GetProps('', [ "$Args{homesetns}:$HomeSet", 'D:href' ])) {
561 0           $Self->SetURL(uri_unescape($Homeset));
562 0           return $Self->{url};
563             }
564              
565 0           croak "Error finding $HomeSet home set at '$OriginalURL'";
566             }
567              
568             =head2 $Self->genuuid()
569              
570             Helper to generate a uuid string. Returns a UUID, e.g.
571              
572             my $uuid = $DAVTalk->genuuid(); # 9b9d68af-ad13-46b8-b7ab-30ab70da14ac
573              
574             =cut
575              
576             sub genuuid {
577 0     0 1   my $Self = shift;
578 0           return "$uuid";
579             }
580              
581             =head2 $Self->auth_header()
582              
583             Generate the authentication header to use on requests:
584              
585             e.g:
586              
587             $Headers{'Authorization'} = $Self->auth_header();
588              
589             =cut
590              
591             sub auth_header {
592 0     0 1   my $Self = shift;
593              
594 0 0         if ($Self->{password}) {
595 0           return 'Basic ' . encode_base64("$Self->{user}:$Self->{password}", '');
596             }
597              
598 0 0         if ($Self->{access_token}) {
599 0           return "Bearer $Self->{access_token}";
600             }
601              
602 0           croak "Need a method to authenticate user (password or access_token)";
603             }
604              
605             =head2 $Self->request_url()
606              
607             Generate the authentication header to use on requests:
608              
609             e.g:
610              
611             $Headers{'Authorization'} = $Self->auth_header();
612              
613             =cut
614              
615             sub request_url {
616 0     0 1   my $Self = shift;
617 0           my $Path = shift;
618              
619 0           my $URL = $Self->{url};
620              
621             # If a reference, assume absolute
622 0 0         if (ref $Path) {
623 0           ($URL, $Path) = $$Path =~ m{(^https?://[^/]+)(.*)$};
624             }
625              
626 0 0         if ($Path) {
627 0           $Path = join "/", map { uri_escape $_ } split m{/}, $Path, -1;
  0            
628 0 0         if ($Path =~ m{^/}) {
629 0           $URL =~ s{(^https?://[^/]+)(.*)}{$1$Path};
630             }
631             else {
632 0           $URL =~ s{/$}{};
633 0           $URL .= "/$Path";
634             }
635             }
636              
637 0           return $URL;
638             }
639              
640             =head2 $Self->NS()
641              
642             Returns a hashref of the 'xmlns:shortname' => 'full namespace' items for use in XML::Spice body generation, e.g.
643              
644             $DAVTalk->Request(
645             'MKCALENDAR',
646             "$calendarId/",
647             x('C:mkcalendar', $Self->NS(),
648             x('D:set',
649             x('D:prop', @Properties),
650             ),
651             ),
652             );
653              
654             # { 'xmlns:C' => 'urn:ietf:params:xml:ns:caldav', 'xmlns:D' => 'DAV:' }
655              
656             =cut
657              
658             sub NS {
659 0     0 1   my $Self = shift;
660              
661             return {
662 0           map { ( "xmlns:$_" => $Self->ns($_) ) }
  0            
663             $Self->ns(),
664             };
665             }
666              
667              
668             =head2 $Self->ns($key, $value)
669              
670             Get or set namespace aliases, e.g
671              
672             $Self->ns(C => 'urn:ietf:params:xml:ns:caldav');
673             my $NS_C = $Self->ns('C'); # urn:ietf:params:xml:ns:caldav
674              
675             =cut
676              
677             sub ns {
678 0     0 1   my $Self = shift;
679              
680             # case: keys
681 0 0         return keys %{$Self->{ns}} unless @_;
  0            
682              
683 0           my $key = shift;
684             # case read one
685 0 0         return $Self->{ns}{$key} unless @_;
686              
687             # case write
688 0           my $prev = $Self->{ns}{$key};
689 0           $Self->{ns}{$key} = shift;
690 0           return $prev;
691             }
692              
693             =head2 function2
694              
695             =cut
696              
697             =head1 AUTHOR
698              
699             Bron Gondwana, C<< <brong at cpan.org> >>
700              
701             =head1 BUGS
702              
703             Please report any bugs or feature requests to C<bug-net-davtalk at rt.cpan.org>, or through
704             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Net-DAVTalk>. I will be notified, and then you'll
705             automatically be notified of progress on your bug as I make changes.
706              
707              
708              
709              
710             =head1 SUPPORT
711              
712             You can find documentation for this module with the perldoc command.
713              
714             perldoc Net::DAVTalk
715              
716              
717             You can also look for information at:
718              
719             =over 4
720              
721             =item * RT: CPAN's request tracker (report bugs here)
722              
723             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-DAVTalk>
724              
725             =item * AnnoCPAN: Annotated CPAN documentation
726              
727             L<http://annocpan.org/dist/Net-DAVTalk>
728              
729             =item * CPAN Ratings
730              
731             L<http://cpanratings.perl.org/d/Net-DAVTalk>
732              
733             =item * Search CPAN
734              
735             L<http://search.cpan.org/dist/Net-DAVTalk/>
736              
737             =back
738              
739              
740             =head1 ACKNOWLEDGEMENTS
741              
742              
743             =head1 LICENSE AND COPYRIGHT
744              
745             Copyright 2015 FastMail Pty. Ltd.
746              
747             This program is free software; you can redistribute it and/or modify it
748             under the terms of the the Artistic License (2.0). You may obtain a
749             copy of the full license at:
750              
751             L<http://www.perlfoundation.org/artistic_license_2_0>
752              
753             Any use, modification, and distribution of the Standard or Modified
754             Versions is governed by this Artistic License. By using, modifying or
755             distributing the Package, you accept this license. Do not use, modify,
756             or distribute the Package, if you do not accept this license.
757              
758             If your Modified Version has been derived from a Modified Version made
759             by someone other than you, you are nevertheless required to ensure that
760             your Modified Version complies with the requirements of this license.
761              
762             This license does not grant you the right to use any trademark, service
763             mark, tradename, or logo of the Copyright Holder.
764              
765             This license includes the non-exclusive, worldwide, free-of-charge
766             patent license to make, have made, use, offer to sell, sell, import and
767             otherwise transfer the Package with respect to any patent claims
768             licensable by the Copyright Holder that are necessarily infringed by the
769             Package. If you institute patent litigation (including a cross-claim or
770             counterclaim) against any party alleging that the Package constitutes
771             direct or contributory patent infringement, then this Artistic License
772             to you shall terminate on the date that such litigation is filed.
773              
774             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
775             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
776             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
777             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
778             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
779             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
780             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
781             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
782              
783              
784             =cut
785              
786             1; # End of Net::DAVTalk