Skip Menu |

This queue is for tickets about the Data-ICal CPAN distribution.

Report information
The Basics
Id: 56892
Status: resolved
Priority: 0/
Queue: Data-ICal

People
Owner: Nobody in particular
Requestors: Nathan.Bailey [...] its.monash.edu
Cc:
AdminCc:

Bug Information
Severity: Wishlist
Broken in: (no value)
Fixed in: (no value)



Subject: Data::ICal::Entry::Event doesn't generate UIDs, but RFC2445 requires them?
Date: Sat, 24 Apr 2010 23:51:39 +1000
To: bug-Data-ICal [...] rt.cpan.org
From: Nathan Bailey <Nathan.Bailey [...] its.monash.edu>
The following perl script (based on Data::ICal::Entry::Event's POD), doesn't generate a UID as part of the event: use Data::ICal; use Data::ICal::Entry::Event; use Date::ICal; my $calendar = Data::ICal->new(); my $vevent = Data::ICal::Entry::Event->new(); $vevent->add_properties( summary => "my party", description => "I'll cry if I want to", # Dat*e*::ICal is not a typo here dtstart => Date::ICal->new( epoch => time )->ical, ); $calendar->add_entry($vevent); print $calendar->as_string; but RFC2445 requires them: 4.8.4.7 Unique Identifier Property Name: UID Purpose: This property defines the persistent, globally unique identifier for the calendar component. Value Type: TEXT Property Parameters: Non-standard property parameters can be specified on this property. Conformance: The property MUST be specified in the "VEVENT", "VTODO", "VJOURNAL" or "VFREEBUSY" calendar components. (See also http://rt.cpan.org/Public/Bug/Display.html?id=56689 ) thanks! :-) N
On Sat Apr 24 09:51:58 2010, Nathan.Bailey@its.monash.edu wrote: Show quoted text
> The following perl script (based on Data::ICal::Entry::Event's POD), > doesn't generate a UID as part of the event:
UIDs need to be persistent for an event across each calendar generated, so I suspect Data::ICal's original intent is to leave them to the developer (you! ;) to handle. Data::ICal's support is fairly low level. While the library could start checking that a UID is specified before output, that would break countless existing code which doesn't specify (however wrongly) a UID. Support for mandatory unique properties is in the distribution, so if you'd like to write a "strict" mode for Data::ICal that defaults off but can be enabled, I'm sure we'd take a patch. We'd probably also consider a patch for optionally auto- generating UIDs if you'd like to roll one.
On Mon Oct 15 18:51:32 2012, TSIBLEY wrote: Show quoted text
> On Sat Apr 24 09:51:58 2010, Nathan.Bailey@its.monash.edu wrote: > We'd probably also consider a patch for optionally auto- > generating UIDs if you'd like to roll one.
I'm currently using an md5_base64 hash of the description followed by '@hostname', but many people probably don't use the description, so perhaps an md5_base64 hash of the summary? Or perhaps summary + dtstart?
Attached are a couple of patches to Data/ICal.pm and Data/ICal/Entry.pm that mean that Data::ICal now supports: 1. An optional calendar name (per the non-RFC x-wr-calname) 2. An optional rfc_strict mode that requires UIDs for certain events, per RFC2445 3. An optional auto_gen mode that generates persistently unique UIDs using md5 checksums and the hostname (requiring two new modules, Digest::MD5 and Sys::Hostname - I believe these are both installed by default so hopefully their inclusion is not too onerous). N PS: I also emailed this to the RT address but it didn't appear - perhaps it doesn't like attachments?
Subject: ICal.pm
use warnings; use strict; package Data::ICal; use base qw/Data::ICal::Entry/; use Class::ReturnValue; use Text::vFile::asData; our $VERSION = '0.18'; use Carp; =head1 NAME Data::ICal - Generates iCalendar (RFC 2445) calendar files =head1 SYNOPSIS use Data::ICal; my $calendar = Data::ICal->new(); my $vtodo = Data::ICal::Entry::Todo->new(); $vtodo->add_properties( # ... see Data::ICal::Entry::Todo documentation ); # ... or $calendar = Data::ICal->new(filename => 'foo.ics'); # parse existing file $calendar = Data::ICal->new(data => 'BEGIN:VCALENDAR...'); # parse from scalar $calendar->add_entry($vtodo); print $calendar->as_string; =head1 DESCRIPTION A L<Data::ICal> object represents a C<VCALENDAR> object as defined in the iCalendar protocol (RFC 2445, MIME type "text/calendar"), as implemented in many popular calendaring programs such as Apple's iCal. Each L<Data::ICal> object is a collection of "entries", which are objects of a subclass of L<Data::ICal::Entry>. The types of entries defined by iCalendar (which refers to them as "components") include events, to-do items, journal entries, free/busy time indicators, and time zone descriptors; in addition, events and to-do items can contain alarm entries. (Currently, L<Data::ICal> only implements to-do items and events.) L<Data::ICal> is a subclass of L<Data::ICal::Entry>; see its manpage for more methods applicable to L<Data::ICal>. =head1 METHODS =cut =head2 new [ data => $data, ] [ filename => $file ], [ calname => $string ], [ vcal10 => $bool ], [ rfc_strict => $bool ], [ auto_uid => $bool ] Creates a new L<Data::ICal> object. If it is given a filename or data argument is passed, then this parses the content of the file or string into the object. If the C<vcal10> flag is passed, parses it according to vCalendar 1.0, not iCalendar 2.0; this in particular impacts the parsing of continuation lines in quoted-printable sections. If a calname is passed, sets x-wr-calname to the given string. Although not specified in RFC2445, most calendar software respects x-wr-calname as the displayed name of the calendar. If the C<rfc_strict> flag is set to true, will require Data::ICal to include UIDs, as per RFC2445, "4.8.4.7 Unique Identifier ... The property MUST be specified in the "VEVENT", "VTODO", "VJOURNAL" or "VFREEBUSY" calendar components" If the C<auto_uid> flag is set to true, will automatically generate a persistently unique UID for this event (within the configues of MD5 uniqueness). If a filename or data argument is not passed, this just sets the object's C<VERSION> and C<PRODID> properties to "2.0" (or "1.0" if the C<vcal10> flag is passed) and the value of the C<product_id> method respectively. Returns a false value upon failure to open or parse the file or data; this false value is a L<Class::ReturnValue> object and can be queried as to its C<error_message>. =cut sub new { my $class = shift; my $self = $class->SUPER::new(@_); my %args = ( filename => undef, calname => undef, data => undef, vcal10 => 0, rfc_strict => 0, auto_uid => 0, @_ ); $self->vcal10( $args{vcal10} ); $self->rfc_strict( $args{rfc_strict} ); $self->auto_uid( $args{auto_uid} ); if ( defined $args{filename} or defined $args{data} ) { # might return a Class::ReturnValue if parsing fails return $self->parse(%args); } else { $self->add_properties( version => ( $self->vcal10 ? '1.0' : '2.0' ), prodid => $self->product_id, 'x-wr-calname' => $args{calname}, ); return $self; } } =head2 parse [ data => $data, ] [ filename => $file, ] Parse a C<.ics> file or string containing one, and populate C<$self> with its contents. Should only be called once on a given object, and will be automatically called by C<new> if you provide arguments to C<new>. Returns C<$self> on success. Returns a false value upon failure to open or parse the file or data; this false value is a L<Class::ReturnValue> object and can be queried as to its C<error_message>. =cut sub parse { my $self = shift; my %args = ( filename => undef, data => undef, @_ ); unless ( defined $args{filename} or defined $args{data} ) { return $self->_error( "parse called with no filename or data specified"); } my @lines; # open the file (checking as we go, like good little Perl mongers) if ( defined $args{filename} ) { open my $fh, '<', $args{filename} or return $self->_error("could not open '$args{filename}': $!"); @lines = map { chomp; $_ } <$fh>; } else { @lines = split /\r?\n/, $args{data}; } @lines = $self->_vcal10_input_cleanup(@lines) if $self->vcal10; # Parse the lines; Text::vFile doesn't want trailing newlines my $cal = eval { Text::vFile::asData->new->parse_lines(@lines) }; return $self->_error("parse failure: $@") if $@; return $self->_error("parse failure") unless $cal and exists $cal->{objects}; # loop through all the vcards foreach my $object ( @{ $cal->{objects} } ) { $self->parse_object($object); } my $version_ref = $self->property("version"); my $version = $version_ref ? $version_ref->[0]->value : undef; unless ( defined $version ) { return $self->_error("data does not specify a version property"); } if ( $version eq '1.0' and not $self->vcal10 or $version eq '2.0' and $self->vcal10 ) { return $self->_error( 'application claims data is' . ( $self->vcal10 ? '' : ' not' ) . ' vCal 1.0 but doc contains VERSION:' . $version ); } return $self; } sub _error { my $self = shift; my $msg = shift; my $ret = Class::ReturnValue->new; $ret->as_error( errno => 1, message => $msg ); return $ret; } =head2 ical_entry_type Returns C<VCALENDAR>, its iCalendar entry name. =cut sub ical_entry_type {'VCALENDAR'} =head2 product_id Returns the product ID used in the calendar's C<PRODID> property; you may wish to override this in a subclass for your own application. =cut sub product_id { my $self = shift; return "Data::ICal $VERSION"; } =head2 mandatory_unique_properties According to the iCalendar standard, the following properties must be specified exactly one time for a calendar: prodid version =cut sub mandatory_unique_properties { qw( prodid version ); } =head2 optional_unique_properties According to the iCalendar standard, the following properties may be specified at most one time for a calendar: calscale method =cut sub optional_unique_properties { qw( calscale method ); } # In quoted-printable sections, convert from vcal10 "=\n" line endings to # ical20 "\n ". sub _vcal10_input_cleanup { my $self = shift; my @in_lines = @_; my @out_lines; my $in_qp = 0; LINE: while (@in_lines) { my $line = shift @in_lines; if ( not $in_qp and $line =~ /^[^:]+;ENCODING=QUOTED-PRINTABLE/i ) { $in_qp = 1; } unless ($in_qp) { push @out_lines, $line; next LINE; } if ( $line =~ s/=$// ) { push @out_lines, $line; $in_lines[0] = ' ' . $in_lines[0] if @in_lines; } else { push @out_lines, $line; $in_qp = 0; } } return @out_lines; } =head1 DEPENDENCIES L<Data::ICal> requires L<Class::Accessor>, L<Text::vFile::asData>, L<MIME::QuotedPrint>, and L<Class::ReturnValue>. =head1 BUGS AND LIMITATIONS L<Data::ICal> does not support time zone daylight or standard entries, so time zone components are basically useless. While L<Data::ICal> tries to check which properties are required and repeatable, this only works in simple cases; it does not check for properties that must either both exist or both not exist, or for mutually exclusive properties. L<Data::ICal> does not check to see if property parameter names are known in general or allowed on the particular property. L<Data::ICal> does not check to see if nested entries are nested properly (alarms in todos and events only, everything else in calendars only). The only property encoding supported by L<Data::ICal> is quoted printable. There is no L<Data::ICal::Entry::Alarm> base class. Please report any bugs or feature requests to C<bug-data-ical@rt.cpan.org>, or through the web interface at L<http://rt.cpan.org>. =head1 AUTHOR Jesse Vincent C<< <jesse@bestpractical.com> >> with David Glasser, Simon Wistow, and Alex Vandiver =head1 LICENCE AND COPYRIGHT Copyright (c) 2005 - 2009, Best Practical Solutions, LLC. All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L<perlartistic>. =head1 DISCLAIMER OF WARRANTY BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. =cut 1;
Subject: Entry.pm

Message body is not shown because it is too large.

Subject: Re: [rt.cpan.org #56892] Entry UIDs are required by RFC2445 but not automatically generated
Date: Tue, 16 Oct 2012 10:36:06 -0700
To: bug-Data-ICal [...] rt.cpan.org
From: Thomas Sibley <trs [...] bestpractical.com>
On 10/15/2012 10:13 PM, Nathan Bailey via RT wrote: Show quoted text
> On Mon Oct 15 18:51:32 2012, TSIBLEY wrote:
>> We'd probably also consider a patch for optionally auto- >> generating UIDs if you'd like to roll one.
> > I'm currently using an md5_base64 hash of the description followed by '@hostname', but many > people probably don't use the description, so perhaps an md5_base64 hash of the summary? Or > perhaps summary + dtstart?
Perhaps. There is also a suggested algorithm in the RFC. Did you take a look at it? Thanks for the code! The updates you submitted are more likely to be reviewed and applied if you reformat them as actual individual patches rather than the entire updated files. Even more so if they're the output of `git format-patch` with well written commit messages explaining the reason for the change.
Subject: Re: [rt.cpan.org #56892] Entry UIDs are required by RFC2445 but not automatically generated
Date: Wed, 17 Oct 2012 22:55:26 +1100
To: bug-Data-ICal [...] rt.cpan.org
From: Nathan Bailey <nathan.bailey [...] monash.edu>
On 17 October 2012 04:36, trs@bestpractical.com via RT <bug-Data-ICal@rt.cpan.org> wrote: Show quoted text
> <URL: https://rt.cpan.org/Ticket/Display.html?id=56892 >
>> I'm currently using an md5_base64 hash of the description followed by '@hostname', but many >> people probably don't use the description, so perhaps an md5_base64 hash of the summary? Or >> perhaps summary + dtstart?
> > Perhaps. There is also a suggested algorithm in the RFC. Did you take > a look at it?
Yes, but I tripped up on "persistent" On re-reading it, I don't think they quite mean it in the same way I was interpreting it; if a program generates the same calendar event, it will have a different UID using that algorithm. But perhaps, in a space-time way, it is a different calendar event, merely with the same content :-) Show quoted text
> Thanks for the code! The updates you submitted are more likely to be > reviewed and applied if you reformat them as actual individual patches > rather than the entire updated files. Even more so if they're the > output of `git format-patch` with well written commit messages > explaining the reason for the change.
Sure, where is the git repository?
Subject: Re: [rt.cpan.org #56892] Entry UIDs are required by RFC2445 but not automatically generated
Date: Wed, 17 Oct 2012 09:49:25 -0700
To: bug-Data-ICal [...] rt.cpan.org
From: Thomas Sibley <trs [...] bestpractical.com>
On 10/17/2012 04:55 AM, Nathan Bailey via RT wrote: Show quoted text
> Yes, but I tripped up on "persistent" On re-reading it, I don't think > they quite mean it in the same way I was interpreting it; if a program > generates the same calendar event, it will have a different UID using > that algorithm. But perhaps, in a space-time way, it is a different > calendar event, merely with the same content :-)
Hmmm, OK. I only saw the start of the section, but didn't read far enough. Show quoted text
> Sure, where is the git repository?
https://github.com/bestpractical/data-ical
Fixed in 0.22, using patches based on the ones you provided.