From ed6f4ccbf19b8fb6c5c10eb2a7eed6a40f406b7f Mon Sep 17 00:00:00 2001 From: Ken Murchison Date: Fri, 13 Dec 2024 08:15:01 -0500 Subject: [PATCH] jmap_contact.c: base64url encode vCard UIDs as JMAP ids --- .../addressbook_set_destroy_contents | 12 +- .../JMAPContacts/card_get_localizations | 8 +- .../JMAPContacts/card_get_ordered_phonetics | 10 +- .../JMAPContacts/card_get_phonetics | 8 +- .../tiny-tests/JMAPContacts/card_get_v3 | 8 +- .../tiny-tests/JMAPContacts/card_get_v4 | 8 +- cassandane/tiny-tests/JMAPContacts/card_query | 10 +- .../tiny-tests/JMAPContacts/card_query_shared | 10 +- .../tiny-tests/JMAPContacts/card_set_state | 6 +- .../tiny-tests/JMAPContacts/card_set_update | 9 +- .../JMAPContacts/card_set_update_media_blob | 5 +- .../tiny-tests/JMAPContacts/cardgroup_get_v3 | 10 +- .../tiny-tests/JMAPContacts/cardgroup_get_v4 | 10 +- .../tiny-tests/JMAPContacts/cardgroup_query | 12 +- .../JMAPContacts/cardgroup_set_create | 6 +- .../JMAPContacts/cardgroup_set_update | 7 +- .../tiny-tests/JMAPContacts/contact_query_uid | 2 +- .../tiny-tests/JMAPContacts/contact_set | 2 +- .../tiny-tests/JMAPContacts/contact_set_uid | 6 +- .../contact_update_grouped_property | 7 +- .../contactgroup_get_deduplicate_contactids | 10 +- .../JMAPContacts/contactgroup_get_v4 | 8 +- .../JMAPContacts/contactgroup_query | 2 +- .../JMAPContacts/contactgroup_query_uid | 2 +- .../JMAPContacts/contactgroup_set_uid | 6 +- .../JMAPContacts/contactgroup_set_update_v4 | 24 ++- .../tiny-tests/JMAPContacts/misc_categories | 9 +- imap/jmap_contact.c | 139 ++++++++++++------ 28 files changed, 213 insertions(+), 143 deletions(-) diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_set_destroy_contents b/cassandane/tiny-tests/JMAPContacts/addressbook_set_destroy_contents index ff2b811641..503bd030bc 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_set_destroy_contents +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_set_destroy_contents @@ -25,15 +25,17 @@ END:VCARD EOF my $VCard = Net::CardDAVTalk::VCard->new_fromstring($card); - my $cardId = basename($carddav->NewContact($abookId, $VCard), '.vcf'); + $carddav->NewContact($abookId, $VCard); + + my $res = $jmap->CallMethods([['Contact/get', {}, "R1"]]); + my $cardId = $res->[0][1]{list}[0]{id}; xlog "Destroy addressbook (with and without onDestroyRemoveContents)"; - my $res = $jmap->CallMethods([ + $res = $jmap->CallMethods([ ['AddressBook/set', { destroy => [$abookId], }, 'R1'], -# XXX Change to ContactCard/get once implemented - ['Contact/get', { + ['ContactCard/get', { ids => [$cardId], properties => ['id'], }, 'R2'], @@ -41,7 +43,7 @@ EOF destroy => [$abookId], onDestroyRemoveContents => JSON::true, }, 'R3'], - ['Contact/get', { + ['ContactCard/get', { ids => [$cardId], properties => ['id'], }, 'R2'], diff --git a/cassandane/tiny-tests/JMAPContacts/card_get_localizations b/cassandane/tiny-tests/JMAPContacts/card_get_localizations index afc454219e..e143243a36 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_get_localizations +++ b/cassandane/tiny-tests/JMAPContacts/card_get_localizations @@ -23,12 +23,12 @@ sub test_card_get_localizations # Sample card from RFC 6350 # Second N suffix removed due to vparse bug # PROP-IDs added so we can easily compare the results - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $uid = 'urn:uuid:ae2640cc-234a-4dd9-95cc-3106258445b9'; my $href = "Default/test.vcf"; my $card = < '1.0', addressBookId => 'Default', 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, + uid => $uid, kind => 'individual', language => 'es', vCardProps => [ @@ -127,6 +126,7 @@ EOF my $have_jscard = $res->[0][1]{list}[0]; # Delete generated fields + delete $have_jscard->{id}; delete $have_jscard->{blobId}; delete $have_jscard->{'cyrusimap.org:blobId'}; delete $have_jscard->{'cyrusimap.org:size'}; diff --git a/cassandane/tiny-tests/JMAPContacts/card_get_ordered_phonetics b/cassandane/tiny-tests/JMAPContacts/card_get_ordered_phonetics index e9361bd4dd..faf8081aa5 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_get_ordered_phonetics +++ b/cassandane/tiny-tests/JMAPContacts/card_get_ordered_phonetics @@ -20,12 +20,12 @@ sub test_card_get_ordered_phonetics expandurl => 1, ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $uid = 'urn:uuid:ae2640cc-234a-4dd9-95cc-3106258445b9'; my $href = "Default/test.vcf"; my $card = < '1.0', addressBookId => 'Default', 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, + uid => $uid, created => '2023-08-24T14:36:19Z', vCardProps => [ [ 'version', {}, 'text', '4.0' ] @@ -65,6 +64,7 @@ EOF my $have_jscard = $res->[0][1]{list}[0]; # Delete generated fields + delete $have_jscard->{id}; delete $have_jscard->{blobId}; delete $have_jscard->{'cyrusimap.org:blobId'}; delete $have_jscard->{'cyrusimap.org:size'}; @@ -73,7 +73,5 @@ EOF normalize_jscard($want_jscard); normalize_jscard($have_jscard); -warn Dumper($want_jscard); -warn Dumper($have_jscard); $self->assert_deep_equals($want_jscard, $have_jscard); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_get_phonetics b/cassandane/tiny-tests/JMAPContacts/card_get_phonetics index 94d908574e..b87b48ef34 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_get_phonetics +++ b/cassandane/tiny-tests/JMAPContacts/card_get_phonetics @@ -20,12 +20,12 @@ sub test_card_get_phonetics expandurl => 1, ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $uid = 'urn:uuid:ae2640cc-234a-4dd9-95cc-3106258445b9'; my $href = "Default/test.vcf"; my $card = < '1.0', addressBookId => 'Default', 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, + uid => $uid, language => 'zho-Hant', vCardProps => [ [ 'version', {}, 'text', '4.0' ] @@ -77,6 +76,7 @@ EOF my $have_jscard = $res->[0][1]{list}[0]; # Delete generated fields + delete $have_jscard->{id}; delete $have_jscard->{blobId}; delete $have_jscard->{'cyrusimap.org:blobId'}; delete $have_jscard->{'cyrusimap.org:size'}; diff --git a/cassandane/tiny-tests/JMAPContacts/card_get_v3 b/cassandane/tiny-tests/JMAPContacts/card_get_v3 index 4d753d2f01..13523e417a 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_get_v3 +++ b/cassandane/tiny-tests/JMAPContacts/card_get_v3 @@ -20,12 +20,12 @@ sub test_card_get_v3 ); # PROP-IDs added so we can easily compare the results - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $uid = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; my $href = "Default/test.vcf"; my $card = < '1.0', addressBookId => 'Default', 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, + uid => $uid, updated => '2008-04-24T19:52:43Z', vCardProps => [ [ 'version', {}, 'text', '3.0' ] @@ -115,6 +114,7 @@ EOF $self->assert_not_null($blobid); # Delete generated fields + delete $have_jscard->{id}; delete $have_jscard->{blobId}; delete $have_jscard->{media}{P1}{blobId}; delete $have_jscard->{'cyrusimap.org:blobId'}; diff --git a/cassandane/tiny-tests/JMAPContacts/card_get_v4 b/cassandane/tiny-tests/JMAPContacts/card_get_v4 index 29460fa108..6c9dd5184f 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_get_v4 +++ b/cassandane/tiny-tests/JMAPContacts/card_get_v4 @@ -22,12 +22,12 @@ sub test_card_get_v4 # Sample card from RFC 6350 # Second N suffix removed due to vparse bug # PROP-IDs added so we can easily compare the results - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $uid = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; my $href = "Default/test.vcf"; my $card = < '1.0', addressBookId => 'Default', 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, + uid => $uid, kind => 'individual', updated => '2023-04-22T19:46:39Z', vCardProps => [ @@ -214,6 +213,7 @@ EOF $self->assert_not_null($s_blobid); # Delete generated fields + delete $have_jscard->{id}; delete $have_jscard->{blobId}; delete $have_jscard->{media}{P1}{blobId}; delete $have_jscard->{media}{S1}{blobId}; diff --git a/cassandane/tiny-tests/JMAPContacts/card_query b/cassandane/tiny-tests/JMAPContacts/card_query index b6d25c4689..5185836bf5 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_query +++ b/cassandane/tiny-tests/JMAPContacts/card_query @@ -203,20 +203,24 @@ sub test_card_query my $id2 = $res->[0][1]{created}{"2"}{id}; my $id3 = $res->[0][1]{created}{"3"}{id}; my $id4 = $res->[0][1]{created}{"4"}{id}; + my $uid1 = $res->[0][1]{created}{"1"}{uid}; + my $uid2 = $res->[0][1]{created}{"2"}{uid}; + my $uid3 = $res->[0][1]{created}{"3"}{uid}; + my $uid4 = $res->[0][1]{created}{"4"}{uid}; xlog $self, "create card groups"; $res = $jmap->CallMethods([['ContactCard/set', {create => { "1" => { kind => 'group', name => { full => "group1" }, - members => { $id1 => JSON::true, $id2 => JSON::true } + members => { $uid1 => JSON::true, $uid2 => JSON::true } }, "2" => { kind => 'group', name => { full => "group2" }, - members => { $id3 => JSON::true } + members => { $uid3 => JSON::true } }, "3" => { kind => 'group', name => { full => "group3" }, - members => { $id4 => JSON::true } + members => { $uid4 => JSON::true } } }}, "R1"]]); diff --git a/cassandane/tiny-tests/JMAPContacts/card_query_shared b/cassandane/tiny-tests/JMAPContacts/card_query_shared index 55f8d1053a..fb302599e6 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_query_shared +++ b/cassandane/tiny-tests/JMAPContacts/card_query_shared @@ -199,6 +199,10 @@ sub test_card_query_shared my $id2 = $res->[0][1]{created}{"card2"}{id}; my $id3 = $res->[0][1]{created}{"card3"}{id}; my $id4 = $res->[0][1]{created}{"card4"}{id}; + my $uid1 = $res->[0][1]{created}{"card1"}{uid}; + my $uid2 = $res->[0][1]{created}{"card2"}{uid}; + my $uid3 = $res->[0][1]{created}{"card3"}{uid}; + my $uid4 = $res->[0][1]{created}{"card4"}{uid}; xlog $self, "create card groups"; $res = $jmap->CallMethods([ [ @@ -209,17 +213,17 @@ sub test_card_query_shared group1 => { kind => 'group', name => { full => "group1" }, - members => { $id1 => JSON::true, $id2 => JSON::true } + members => { $uid1 => JSON::true, $uid2 => JSON::true } }, group2 => { kind => 'group', name => { full => "group2" }, - members => { $id3 => JSON::true } + members => { $uid3 => JSON::true } }, group3 => { kind => 'group', name => { full => "group3" }, - members => { $id4 => JSON::true } + members => { $uid4 => JSON::true } } } }, diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_state b/cassandane/tiny-tests/JMAPContacts/card_set_state index 46f92ecf73..a17e85358d 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_state +++ b/cassandane/tiny-tests/JMAPContacts/card_set_state @@ -10,7 +10,7 @@ sub test_card_set_state xlog $self, "create contact"; my $name = 'Mr. John Q. Public, Esq.'; - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $uid = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; my $res = $jmap->CallMethods([ ['ContactCard/set', { @@ -18,7 +18,7 @@ sub test_card_set_state "1" => { '@type' => 'Card', version => '1.0', - uid => $id, + uid => $uid, name => { full => $name } } } @@ -27,7 +27,7 @@ sub test_card_set_state $self->assert_not_null($res); $self->assert_str_equals('ContactCard/set', $res->[0][0]); $self->assert_str_equals('R1', $res->[0][2]); - $id = $res->[0][1]{created}{"1"}{id}; + my $id = $res->[0][1]{created}{"1"}{id}; my $state = $res->[0][1]{newState}; xlog $self, "get contact $id"; diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_update b/cassandane/tiny-tests/JMAPContacts/card_set_update index 892395a633..f9e6b563ae 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_update +++ b/cassandane/tiny-tests/JMAPContacts/card_set_update @@ -19,7 +19,7 @@ sub test_card_set_update expandurl => 1, ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $uid = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; my $res = $jmap->CallMethods([ ['ContactCard/set', { @@ -27,7 +27,7 @@ sub test_card_set_update "1" => { '@type' => 'Card', version => '1.0', - uid => $id, + uid => $uid, name => { full => 'John Doe' }, nicknames => { k391 => { @@ -41,6 +41,7 @@ sub test_card_set_update ]); $self->assert_not_null($res->[0][1]{created}{1}); + my $id = $res->[0][1]{created}{1}{id}; my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; $res = $jmap->CallMethods([ @@ -134,7 +135,7 @@ sub test_card_set_update ]); my $abookid = $res->[0][1]{created}{"1"}{id}; - $href = "$abookid/$id.vcf"; + $href = "$abookid/$uid.vcf"; $res = $jmap->CallMethods([ ['ContactCard/set', { @@ -155,7 +156,7 @@ sub test_card_set_update $card = $res->{content}; $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/UID:$id/, $card); + $self->assert_matches(qr/UID:$uid/, $card); $self->assert_matches(qr/NICKNAME;PROP-ID=foo:Doey/, $card); $self->assert_matches(qr/CATEGORIES:foo/, $card); $self->assert_matches(qr/REV:/, $card); diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_update_media_blob b/cassandane/tiny-tests/JMAPContacts/card_set_update_media_blob index 2cb78506ab..31e93d9fe4 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_update_media_blob +++ b/cassandane/tiny-tests/JMAPContacts/card_set_update_media_blob @@ -19,7 +19,7 @@ sub test_card_set_update_media_blob expandurl => 1, ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $uid = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; my $res = $jmap->CallMethods([ ['ContactCard/set', { @@ -27,7 +27,7 @@ sub test_card_set_update_media_blob "1" => { '@type' => 'Card', version => '1.0', - uid => $id, + uid => $uid, name => { full => 'Jane Doe' }, media => { res1 => { @@ -42,6 +42,7 @@ sub test_card_set_update_media_blob ]); $self->assert_not_null($res->[0][1]{created}{1}); + my $id = $res->[0][1]{created}{1}{id}; my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; $res = $carddav->Request('GET', $href, '', diff --git a/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v3 b/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v3 index b86f24b31d..bfab94fd0c 100644 --- a/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v3 +++ b/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v3 @@ -19,15 +19,15 @@ sub test_cardgroup_get_v3 expandurl => 1, ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $uid = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; my $member1 = 'urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af'; my $member2 = 'urn:uuid:b8767877-b4a1-4c70-9acc-505d3819e519'; - my $href = "Default/$id.vcf"; + my $href = "Default/$uid.vcf"; my $card = < '1.0', addressBookId => 'Default', 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, + uid => $uid, kind => 'group', vCardProps => [ [ 'version', {}, 'text', '3.0' ] @@ -67,6 +66,7 @@ EOF my $have_jscard = $res->[0][1]{list}[0]; # Delete generated fields + delete $have_jscard->{id}; delete $have_jscard->{blobId}; delete $have_jscard->{'cyrusimap.org:blobId'}; delete $have_jscard->{'cyrusimap.org:size'}; diff --git a/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v4 b/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v4 index a70c001ea3..f85ee98d50 100644 --- a/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v4 +++ b/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v4 @@ -19,15 +19,15 @@ sub test_cardgroup_get_v4 expandurl => 1, ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $uid = 'urn:uuid:ae2640cc-234a-4dd9-95cc-3106258445b9'; my $member1 = 'urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af'; my $member2 = 'urn:uuid:b8767877-b4a1-4c70-9acc-505d3819e519'; - my $href = "Default/$id.vcf"; + my $href = "Default/$uid.vcf"; my $card = < '1.0', addressBookId => 'Default', 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, + uid => $uid, kind => 'group', vCardProps => [ [ 'version', {}, 'text', '4.0' ] @@ -66,6 +65,7 @@ EOF my $have_jscard = $res->[0][1]{list}[0]; # Delete generated fields + delete $have_jscard->{id}; delete $have_jscard->{blobId}; delete $have_jscard->{'cyrusimap.org:blobId'}; delete $have_jscard->{'cyrusimap.org:size'}; diff --git a/cassandane/tiny-tests/JMAPContacts/cardgroup_query b/cassandane/tiny-tests/JMAPContacts/cardgroup_query index a021c77a67..5a3531247a 100644 --- a/cassandane/tiny-tests/JMAPContacts/cardgroup_query +++ b/cassandane/tiny-tests/JMAPContacts/cardgroup_query @@ -187,20 +187,24 @@ sub test_cardgroup_query my $id2 = $res->[0][1]{created}{"2"}{id}; my $id3 = $res->[0][1]{created}{"3"}{id}; my $id4 = $res->[0][1]{created}{"4"}{id}; + my $uid1 = $res->[0][1]{created}{"1"}{uid}; + my $uid2 = $res->[0][1]{created}{"2"}{uid}; + my $uid3 = $res->[0][1]{created}{"3"}{uid}; + my $uid4 = $res->[0][1]{created}{"4"}{uid}; xlog $self, "create card groups"; $res = $jmap->CallMethods([['ContactCard/set', {create => { "1" => { kind => 'group', name => { full => "group1" }, - members => { $id1 => JSON::true, $id2 => JSON::true } + members => { $uid1 => JSON::true, $uid2 => JSON::true } }, "2" => { kind => 'group', name => { full => "group2" }, - members => { $id3 => JSON::true } + members => { $uid3 => JSON::true } }, "3" => { kind => 'group', name => { full => "group3" }, - members => { $id4 => JSON::true } + members => { $uid4 => JSON::true } } }}, "R1"]]); @@ -236,7 +240,7 @@ sub test_cardgroup_query xlog $self, "filter by member"; $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { kind => "group", hasMember => $id3 } + filter => { kind => "group", hasMember => $uid3 } }, "R1"] ]); $self->assert_num_equals(1, $res->[0][1]{total}); $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); diff --git a/cassandane/tiny-tests/JMAPContacts/cardgroup_set_create b/cassandane/tiny-tests/JMAPContacts/cardgroup_set_create index 9a0dcdfc29..e412ece169 100644 --- a/cassandane/tiny-tests/JMAPContacts/cardgroup_set_create +++ b/cassandane/tiny-tests/JMAPContacts/cardgroup_set_create @@ -22,7 +22,7 @@ sub test_cardgroup_set_create my $name = 'The Doe Family'; my $member1 = "urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af"; my $member2 = "urn:uuid:b8767877-b4a1-4c70-9acc-505d3819e519"; - my $id = 'urn:uuid:ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $uid = 'urn:uuid:ae2640cc-234a-4dd9-95cc-3106258445b9'; my $res = $jmap->CallMethods([ ['ContactCard/set', { @@ -30,7 +30,7 @@ sub test_cardgroup_set_create "1" => { '@type' => 'Card', version => '1.0', - uid => $id, + uid => $uid, kind => 'group', name => { full => $name }, members => { @@ -53,7 +53,7 @@ sub test_cardgroup_set_create $self->assert_matches(qr/VERSION:4.0/, $card); $self->assert_matches(qr/KIND:GROUP/, $card); - $self->assert_matches(qr/UID:$id/, $card); + $self->assert_matches(qr/UID:$uid/, $card); $self->assert_matches(qr/FN:$name/, $card); $self->assert_matches(qr/MEMBER:$member1/, $card); $self->assert_matches(qr/MEMBER:$member2/, $card); diff --git a/cassandane/tiny-tests/JMAPContacts/cardgroup_set_update b/cassandane/tiny-tests/JMAPContacts/cardgroup_set_update index fe3eea95dc..0a5b6e7634 100644 --- a/cassandane/tiny-tests/JMAPContacts/cardgroup_set_update +++ b/cassandane/tiny-tests/JMAPContacts/cardgroup_set_update @@ -22,8 +22,7 @@ sub test_cardgroup_set_update my $name = 'The Doe Family'; my $member1 = "urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af"; my $member2 = "urn:uuid:b8767877-b4a1-4c70-9acc-505d3819e519"; - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/$id.vcf"; + my $uid = 'urn:uuid:ae2640cc-234a-4dd9-95cc-3106258445b9'; my $res = $jmap->CallMethods([ ['ContactCard/set', { @@ -31,7 +30,7 @@ sub test_cardgroup_set_update "1" => { '@type' => 'Card', version => '1.0', - uid => $id, + uid => $uid, kind => 'group', name => { full => $name }, members => { @@ -43,7 +42,9 @@ sub test_cardgroup_set_update ]); $self->assert_not_null($res->[0][1]{created}{1}); + my $id = $res->[0][1]{created}{1}{id}; + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; $res = $carddav->Request('GET', $href, '', 'Accept' => 'text/vcard; version=4.0'); diff --git a/cassandane/tiny-tests/JMAPContacts/contact_query_uid b/cassandane/tiny-tests/JMAPContacts/contact_query_uid index dbd1e1b9cc..98d3627209 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_query_uid +++ b/cassandane/tiny-tests/JMAPContacts/contact_query_uid @@ -76,5 +76,5 @@ sub test_contact_query_uid ]); $self->assert_str_equals("Contact/query", $res->[0][0]); my %gotIds = map { $_ => 1 } @{$res->[0][1]{ids}}; - $self->assert_deep_equals({ $contactUid1 => 1, $contactUid3 => 1, }, \%gotIds); + $self->assert_deep_equals({ $contactId1 => 1, $contactId3 => 1, }, \%gotIds); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set b/cassandane/tiny-tests/JMAPContacts/contact_set index 74ef1db9bc..74538e55cb 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set +++ b/cassandane/tiny-tests/JMAPContacts/contact_set @@ -22,7 +22,7 @@ sub test_contact_set # get expands default values, so do the same manually $contact->{id} = $id; - $contact->{uid} = $id; + $contact->{uid} = $res->[0][1]{created}{"1"}{uid}; $contact->{isFlagged} = JSON::false; $contact->{prefix} = ''; $contact->{suffix} = ''; diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_uid b/cassandane/tiny-tests/JMAPContacts/contact_set_uid index 28a50d77ab..b59e365f74 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_uid +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_uid @@ -39,8 +39,8 @@ sub test_contact_set_uid $self->assert_not_null($res->[1][1]{list}[0]{uid}); my($filename, $dirs, $suffix) = fileparse($res->[1][1]{list}[0]{"x-href"}, ".vcf"); $self->assert_not_null($res->[1][1]{list}[0]->{id}); - $self->assert_str_equals($res->[1][1]{list}[0]->{uid}, $res->[1][1]{list}[0]->{id}); - $self->assert_str_equals($filename, $res->[1][1]{list}[0]->{id}); + $self->assert_str_not_equals($res->[1][1]{list}[0]->{uid}, $res->[1][1]{list}[0]->{id}); + $self->assert_str_equals($filename, $res->[1][1]{list}[0]->{uid}); $jmap->{CreatedIds} = {}; # A non-pathsafe UID maps to uid but not the DAV resource. @@ -59,7 +59,7 @@ sub test_contact_set_uid $self->assert_not_null($res->[1][1]{list}[0]{uid}); ($filename, $dirs, $suffix) = fileparse($res->[1][1]{list}[0]{"x-href"}, ".vcf"); $self->assert_not_null($res->[1][1]{list}[0]->{id}); - $self->assert_str_equals($res->[1][1]{list}[0]->{id}, $res->[1][1]{list}[0]->{uid}); + $self->assert_str_not_equals($res->[1][1]{list}[0]->{id}, $res->[1][1]{list}[0]->{uid}); $self->assert_str_not_equals('path#uid', $filename); $jmap->{CreatedIds} = {}; diff --git a/cassandane/tiny-tests/JMAPContacts/contact_update_grouped_property b/cassandane/tiny-tests/JMAPContacts/contact_update_grouped_property index 59096500c0..b6ccd0259a 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_update_grouped_property +++ b/cassandane/tiny-tests/JMAPContacts/contact_update_grouped_property @@ -19,12 +19,12 @@ sub test_contact_update_grouped_property expandurl => 1, ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/$id.vcf"; + my $uid = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $href = "Default/$uid.vcf"; my $card = <[0][1]{list}[0]{id}; $self->assert_equals("Bubba Gump Shrimp Co.", $res->[0][1]{list}[0]{company}); diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_get_deduplicate_contactids b/cassandane/tiny-tests/JMAPContacts/contactgroup_get_deduplicate_contactids index 3f81e7735d..211139e8dc 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_get_deduplicate_contactids +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_get_deduplicate_contactids @@ -50,13 +50,15 @@ EOF properties => ['contactIds', 'otherAccountContactIds' ], }, 'R1'] ]); - my @gotids = sort @{$res->[0][1]{list}[0]{contactIds}}; xlog "Assert contactIds in group got deduplicated"; - $self->assert_deep_equals(\@wantids, \@gotids); + $self->assert_num_equals(3, scalar @{$res->[0][1]{list}[0]{contactIds}}); + $self->assert_str_not_equals($res->[0][1]{list}[0]{contactIds}[0], + $res->[0][1]{list}[0]{contactIds}[1]); + $self->assert_str_not_equals($res->[0][1]{list}[0]{contactIds}[0], + $res->[0][1]{list}[0]{contactIds}[2]); xlog "Assert otherAccountContactIds got deduplicated"; - $self->assert_deep_equals({ foo => \@wantOtherAccountIds }, - $res->[0][1]{list}[0]{otherAccountContactIds}); + $self->assert_num_equals(1, scalar @{$res->[0][1]{list}[0]{otherAccountContactIds}{foo}}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_get_v4 b/cassandane/tiny-tests/JMAPContacts/contactgroup_get_v4 index 73e37d92b9..dc95ed93f9 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_get_v4 +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_get_v4 @@ -19,12 +19,12 @@ sub test_contactgroup_get_v4 expandurl => 1, ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/$id.vcf"; + my $uid = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $href = "Default/$uid.vcf"; my $card = <assert_str_equals($id, $res->[0][1]{list}[0]{id}); + $self->assert_str_equals($uid, $res->[0][1]{list}[0]{uid}); $self->assert_str_equals('Test', $res->[0][1]{list}[0]{name}); $self->assert_num_equals(2, scalar @{$res->[0][1]{list}[0]{contactIds}}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_query b/cassandane/tiny-tests/JMAPContacts/contactgroup_query index a4d6b959a2..934150ab83 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_query +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_query @@ -76,5 +76,5 @@ sub test_contactgroup_query ]); $self->assert_str_equals("ContactGroup/query", $res->[0][0]); my %gotIds = map { $_ => 1 } @{$res->[0][1]{ids}}; - $self->assert_deep_equals({ $contactGroupUid2 => 1, $contactGroupUid3 => 1, }, \%gotIds); + $self->assert_deep_equals({ $contactGroupId2 => 1, $contactGroupId3 => 1, }, \%gotIds); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_query_uid b/cassandane/tiny-tests/JMAPContacts/contactgroup_query_uid index c8fa5c612b..50741a055f 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_query_uid +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_query_uid @@ -76,5 +76,5 @@ sub test_contactgroup_query_uid ]); $self->assert_str_equals("ContactGroup/query", $res->[0][0]); my %gotIds = map { $_ => 1 } @{$res->[0][1]{ids}}; - $self->assert_deep_equals({ $contactGroupUid1 => 1, $contactGroupUid3 => 1, }, \%gotIds); + $self->assert_deep_equals({ $contactGroupId1 => 1, $contactGroupId3 => 1, }, \%gotIds); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_set_uid b/cassandane/tiny-tests/JMAPContacts/contactgroup_set_uid index 0c46ca5ef9..cbd1aef350 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_set_uid +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_set_uid @@ -37,8 +37,8 @@ sub test_contactgroup_set_uid $self->assert_not_null($res->[1][1]{list}[0]{uid}); my($filename, $dirs, $suffix) = fileparse($res->[1][1]{list}[0]{"x-href"}, ".vcf"); $self->assert_not_null($res->[1][1]{list}[0]->{id}); - $self->assert_str_equals($res->[1][1]{list}[0]->{uid}, $res->[1][1]{list}[0]->{id}); - $self->assert_str_equals($filename, $res->[1][1]{list}[0]->{id}); + $self->assert_str_not_equals($res->[1][1]{list}[0]->{uid}, $res->[1][1]{list}[0]->{id}); + $self->assert_str_equals($filename, $res->[1][1]{list}[0]->{uid}); $jmap->{CreatedIds} = {}; # A non-pathsafe UID maps to uid but not the DAV resource. @@ -56,7 +56,7 @@ sub test_contactgroup_set_uid $self->assert_not_null($res->[1][1]{list}[0]{uid}); ($filename, $dirs, $suffix) = fileparse($res->[1][1]{list}[0]{"x-href"}, ".vcf"); $self->assert_not_null($res->[1][1]{list}[0]->{id}); - $self->assert_str_equals($res->[1][1]{list}[0]->{id}, $res->[1][1]{list}[0]->{uid}); + $self->assert_str_not_equals($res->[1][1]{list}[0]->{id}, $res->[1][1]{list}[0]->{uid}); $self->assert_str_not_equals('path#uid', $filename); $jmap->{CreatedIds} = {}; diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_set_update_v4 b/cassandane/tiny-tests/JMAPContacts/contactgroup_set_update_v4 index 0f00048f68..7cee9eae73 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_set_update_v4 +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_set_update_v4 @@ -21,19 +21,16 @@ sub test_contactgroup_set_update_v4 expandurl => 1, ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $contact1 = '60f60d95-1f33-480c-bfd6-02b93a07aefc'; - my $contact2 = '3e7cfbaf-3199-41bd-8749-38b8d1c89605'; - my $contact3 = '5b3b9ce1-0b5e-4cbd-8add-018321cad51b'; - my $href = "Default/$id.vcf"; + my $uid = 'urn:uuid:ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $href = "Default/group.vcf"; my $card = <assert_str_equals($id, $res->[0][1]{list}[0]{id}); + my $id = $res->[0][1]{list}[0]{id}; + $self->assert_str_equals($uid, $res->[0][1]{list}[0]{uid}); $self->assert_str_equals('Test', $res->[0][1]{list}[0]{name}); $self->assert_num_equals(3, scalar @{$res->[0][1]{list}[0]{contactIds}}); - $self->assert_str_equals($contact1, $res->[0][1]{list}[0]{contactIds}[0]); - $self->assert_str_equals($contact2, $res->[0][1]{list}[0]{contactIds}[1]); - $self->assert_str_equals($contact3, $res->[0][1]{list}[0]{contactIds}[2]); + my $contact1 = $res->[0][1]{list}[0]{contactIds}[0]; + my $contact2 = $res->[0][1]{list}[0]{contactIds}[1]; + my $contact3 = $res->[0][1]{list}[0]{contactIds}[2]; xlog $self, "update contact group by removing a member and reordering"; $res = $jmap->CallMethods([['ContactGroup/set', {update => { diff --git a/cassandane/tiny-tests/JMAPContacts/misc_categories b/cassandane/tiny-tests/JMAPContacts/misc_categories index f41bb91bdf..794d9a8495 100644 --- a/cassandane/tiny-tests/JMAPContacts/misc_categories +++ b/cassandane/tiny-tests/JMAPContacts/misc_categories @@ -22,12 +22,12 @@ sub test_misc_categories xlog $self, "create a contact with two categories"; - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/$id.vcf"; + my $uid = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $href = "Default/$uid.vcf"; my $card = <Request('GET', $href); $self->assert_matches(qr/cat1,cat2/, $data->{content}); - my $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R2"]]); + my $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); $self->assert_not_null($fetch); $self->assert_str_equals('Contact/get', $fetch->[0][0]); $self->assert_str_equals('R2', $fetch->[0][2]); $self->assert_str_equals('Forrest', $fetch->[0][1]{list}[0]{firstName}); + my $id = $fetch->[0][1]{list}[0]{id}; my $res = $jmap->CallMethods([['Contact/set', { update => {$id => {firstName => "foo"}} }, "R1"]]); diff --git a/imap/jmap_contact.c b/imap/jmap_contact.c index 0d09f1580b..825c638c45 100644 --- a/imap/jmap_contact.c +++ b/imap/jmap_contact.c @@ -360,6 +360,28 @@ static json_t *jmap_utf8string(const char *s) return jval; } +static const char *contactid_to_uid(const char *id) +{ + static struct buf buf = BUF_INITIALIZER; + + if (!id) return NULL; + + buf_reset(&buf); + charset_decode(&buf, id, strlen(id), ENCODING_BASE64URL); + return buf_cstring(&buf); +} + +static const char *contactid_from_uid(const char *uid) +{ + static struct buf buf = BUF_INITIALIZER; + + if (!uid) return NULL; + + buf_reset(&buf); + charset_encode(&buf, uid, strlen(uid), ENCODING_BASE64URL); + return buf_cstring(&buf); +} + /***************************************************************************** * JMAP Contacts API ****************************************************************************/ @@ -425,7 +447,8 @@ static json_t *jmap_group_from_vcard(struct vparse_card *vcard) else if (!strcasecmp(name, "member") || !strcasecmp(name, "x-addressbookserver-member")) { if (strncmp(propval, "urn:uuid:", 9)) continue; - json_object_set_new(contactids_set, propval+9, json_true()); + json_object_set_new(contactids_set, + contactid_from_uid(propval+9), json_true()); } else if (!strcasecmp(name, "x-fm-otheraccount-member")) { @@ -437,7 +460,8 @@ static json_t *jmap_group_from_vcard(struct vparse_card *vcard) object = json_object(); json_object_set_new(otherids_sets, param->value, object); } - json_object_set_new(object, propval+9, json_true()); + json_object_set_new(object, + contactid_from_uid(propval+9), json_true()); } } @@ -509,7 +533,8 @@ static int getgroups_cb(void *rock, struct carddav_data *cdata) vparse_free_card(vcard); - json_object_set_new(obj, "id", json_string(cdata->vcard_uid)); + json_object_set_new(obj, "id", + json_string(contactid_from_uid(cdata->vcard_uid))); json_object_set_new(obj, "uid", json_string(cdata->vcard_uid)); json_object_set_new(obj, "addressbookId", @@ -792,9 +817,9 @@ static int _contacts_get(struct jmap_req *req, carddav_cb_t *cb, int kind, json_t *jval; json_array_foreach(get.ids, i, jval) { rock.rows = 0; - const char *id = json_string_value(jval); + const char *uid = contactid_to_uid(json_string_value(jval)); - r = carddav_get_cards(db, mbentry, req->userid, id, kind, cb, &rock); + r = carddav_get_cards(db, mbentry, req->userid, uid, kind, cb, &rock); if (r || !rock.rows) { json_array_append(get.not_found, jval); } @@ -849,6 +874,7 @@ static int getchanges_cb(void *rock, struct carddav_data *cdata) struct changes_rock *urock = (struct changes_rock *) rock; struct dav_data dav = cdata->dav; const char *uid = cdata->vcard_uid; + const char *id = contactid_from_uid(uid); mbentry_t *mbentry = jmap_mbentry_from_dav(urock->req, &dav); int rights = @@ -867,12 +893,12 @@ static int getchanges_cb(void *rock, struct carddav_data *cdata) /* Report item as updated or destroyed. */ if (dav.alive) { if (dav.createdmodseq <= urock->changes->since_modseq) - json_array_append_new(urock->changes->updated, json_string(uid)); + json_array_append_new(urock->changes->updated, json_string(id)); else - json_array_append_new(urock->changes->created, json_string(uid)); + json_array_append_new(urock->changes->created, json_string(id)); } else { if (dav.createdmodseq <= urock->changes->since_modseq) - json_array_append_new(urock->changes->destroyed, json_string(uid)); + json_array_append_new(urock->changes->destroyed, json_string(id)); } /* Fetch record to determine modseq. */ @@ -974,14 +1000,15 @@ static int _add_group_entries(struct jmap_req *req, for (index = 0; index < json_array_size(members); index++) { const char *item = _json_array_get_string(members, index); - const char *uid = _resolve_contactid(req, item); - if (!item || !uid) { + const char *id = _resolve_contactid(req, item); + if (!item || !id) { buf_printf(&buf, "contactIds[%zu]", index); json_array_append_new(invalid, json_string(buf_cstring(&buf))); buf_reset(&buf); continue; } + const char *uid = contactid_to_uid(id); buf_reset(&buf); if (strncmpsafe("urn:uuid:", uid, 9)) { buf_setcstr(&buf, "urn:uuid:"); @@ -1009,14 +1036,15 @@ static int _add_othergroup_entries(struct jmap_req *req, unsigned i; for (i = 0; i < json_array_size(arg); i++) { const char *item = json_string_value(json_array_get(arg, i)); - const char *uid = _resolve_contactid(req, item); - if (!item || !uid) { + const char *id = _resolve_contactid(req, item); + if (!item || !id) { buf_printf(&buf, "otherAccountContactIds[%s]", key); json_array_append_new(invalid, json_string(buf_cstring(&buf))); buf_reset(&buf); continue; } + const char *uid = contactid_to_uid(id); buf_reset(&buf); if (strncmpsafe("urn:uuid:", uid, 9)) { buf_setcstr(&buf, "urn:uuid:"); @@ -1179,11 +1207,13 @@ static void _contacts_set(struct jmap_req *req, unsigned kind, } /* update */ - const char *uid; - json_object_foreach(set.update, uid, arg) { + const char *id; + json_object_foreach(set.update, id, arg) { json_t *invalid = json_array(); jmap_contact_errors_t errors = { invalid, NULL }; json_t *item = NULL; + const char *uid = contactid_to_uid(id); + r = _set_update(req, kind, uid, arg, db, &mailbox, &item, &errors); if (r) { json_t *err; @@ -1214,7 +1244,7 @@ static void _contacts_set(struct jmap_req *req, unsigned kind, default: err = jmap_server_error(r); } - json_object_set_new(set.not_updated, uid, err); + json_object_set_new(set.not_updated, id, err); r = 0; json_decref(item); json_decref(invalid); @@ -1224,7 +1254,7 @@ static void _contacts_set(struct jmap_req *req, unsigned kind, json_t *err = json_pack("{s:s s:o}", "type", "invalidProperties", "properties", invalid); - json_object_set_new(set.not_updated, uid, err); + json_object_set_new(set.not_updated, id, err); json_decref(errors.blobNotFound); json_decref(item); continue; @@ -1235,28 +1265,30 @@ static void _contacts_set(struct jmap_req *req, unsigned kind, json_t *err = json_pack("{s:s s:o}", "type", "blobNotFound", "notFound", errors.blobNotFound); - json_object_set_new(set.not_updated, uid, err); + json_object_set_new(set.not_updated, id, err); json_decref(item); continue; } /* Report contact as updated. */ - json_object_set_new(set.updated, uid, item); + json_object_set_new(set.updated, id, item); } /* destroy */ size_t index; for (index = 0; index < json_array_size(set.destroy); index++) { - const char *uid = _json_array_get_string(set.destroy, index); - if (!uid) { + const char *id = _json_array_get_string(set.destroy, index); + if (!id) { json_t *err = json_pack("{s:s}", "type", "invalidArguments"); - json_object_set_new(set.not_destroyed, uid, err); + json_object_set_new(set.not_destroyed, id, err); continue; } mbentry_t *mbentry = NULL; struct carddav_data *cdata = NULL; uint32_t olduid; + const char *uid = contactid_to_uid(id); + r = carddav_lookup_uid(db, uid, &cdata); /* is it a valid contact? */ @@ -1264,7 +1296,7 @@ static void _contacts_set(struct jmap_req *req, unsigned kind, (cdata->kind != kind && kind != CARDDAV_KIND_ANY)) { r = 0; json_t *err = json_pack("{s:s}", "type", "notFound"); - json_object_set_new(set.not_destroyed, uid, err); + json_object_set_new(set.not_destroyed, id, err); continue; } olduid = cdata->dav.imap_uid; @@ -1276,7 +1308,7 @@ static void _contacts_set(struct jmap_req *req, unsigned kind, json_t *err = json_pack("{s:s}", "type", rights & JACL_READITEMS ? "accountReadOnly" : "notFound"); - json_object_set_new(set.not_destroyed, uid, err); + json_object_set_new(set.not_destroyed, id, err); mboxlist_entry_free(&mbentry); continue; } @@ -1301,7 +1333,7 @@ static void _contacts_set(struct jmap_req *req, unsigned kind, goto done; } - json_array_append_new(set.destroyed, json_string(uid)); + json_array_append_new(set.destroyed, json_string(id)); } /* force modseq to stable */ @@ -1991,7 +2023,8 @@ static int getcontacts_cb(void *rock, struct carddav_data *cdata) json_integer(record.size - record.header_size)); } - json_object_set_new(obj, "id", json_string(cdata->vcard_uid)); + json_object_set_new(obj, "id", + json_string(contactid_from_uid(cdata->vcard_uid))); json_object_set_new(obj, "uid", json_string(cdata->vcard_uid)); json_object_set_new(obj, "addressbookId", @@ -2458,7 +2491,7 @@ static void *contact_filter_parse(json_t *arg) json_array_foreach(inContactGroup, i, val) { const char *id; if (json_unpack(val, "s", &id) != -1) { - hash_insert(id, (void*)1, f->inContactGroup); + hash_insert(contactid_to_uid(id), (void*)1, f->inContactGroup); } } } @@ -2852,11 +2885,13 @@ static int _contactsquery_cb(void *rock, struct carddav_data *cdata) if (!json_array_size(query->ids)) { query->result_position = query->total - 1; } - json_array_append_new(query->ids, json_string(cdata->vcard_uid)); + json_array_append_new(query->ids, + json_string(contactid_from_uid(cdata->vcard_uid))); } else { /* Keep matching entries for post-processing */ - json_object_set_new(entry, "id", json_string(cdata->vcard_uid)); + json_object_set_new(entry, "id", + json_string(contactid_from_uid(cdata->vcard_uid))); json_object_set_new(entry, "uid", json_string(cdata->vcard_uid)); ptrarray_append(&crock->entries, entry); entry = NULL; @@ -3081,7 +3116,7 @@ static int _contactsquery(struct jmap_req *req, unsigned kind, int i; for (i = 0; i < ptrarray_size(&rock.entries); i++) { json_t *entry = ptrarray_nth(&rock.entries, i); - json_array_append(query.ids, json_object_get(entry, "uid")); + json_array_append(query.ids, json_object_get(entry, "id")); json_decref(entry); } /* Determine start position of result window */ @@ -3833,7 +3868,8 @@ static int _json_to_card(struct jmap_req *req, json_object_foreach(arg, key, jval) { if (cdata) { if (!strcmp(key, "id")) { - if (strcmpnull(cdata->vcard_uid, json_string_value(jval))) { + if (strcmpnull(json_string_value(jval), + contactid_from_uid(cdata->vcard_uid))) { json_array_append_new(invalid, json_string("id")); } continue; @@ -4276,7 +4312,7 @@ static int _contact_set_create(jmap_req_t *req, unsigned kind, json_t *jcard, } r = 0; - json_object_set_new(item, "id", json_string(uid)); + json_object_set_new(item, "id", json_string(contactid_from_uid(uid))); struct index_record record; mailbox_find_index_record(*mailbox, (*mailbox)->i.last_uid, &record); @@ -4446,7 +4482,14 @@ static int _contact_set_update(jmap_req_t *req, unsigned kind, others = json_object_get(jupdated, "otherAccountContactIds"); } } - else if (!strcmp(key, "id") || !strcmp(key, "uid")) { + else if (!strcmp(key, "id")) { + if (cdata && + strcmpnull(contactid_from_uid(cdata->vcard_uid), + json_string_value(jval))) { + json_array_append_new(invalid, json_string(key)); + } + } + else if (!strcmp(key, "uid")) { if (cdata && strcmpnull(cdata->vcard_uid, json_string_value(jval))) { json_array_append_new(invalid, json_string(key)); } @@ -4603,11 +4646,12 @@ static void _contact_copy(jmap_req_t *req, } /* Lookup event */ + const char *src_uid = contactid_to_uid(src_id); struct carddav_data *cdata = NULL; - r = carddav_lookup_uid(src_db, src_id, &cdata); + r = carddav_lookup_uid(src_db, src_uid, &cdata); if (r && r != CYRUSDB_NOTFOUND) { syslog(LOG_ERR, "carddav_lookup_uid(%s) failed: %s", - src_id, error_message(r)); + src_uid, error_message(r)); goto done; } if (r == CYRUSDB_NOTFOUND || !cdata->dav.alive || @@ -4630,7 +4674,7 @@ static void _contact_copy(jmap_req_t *req, r = mailbox_find_index_record(src_mbox, cdata->dav.imap_uid, &record); if (!r) src_card = _from_record(req, src_mbox, &record); if (!src_card) { - syslog(LOG_ERR, "contact_copy: can't convert %s to JMAP", src_id); + syslog(LOG_ERR, "contact_copy: can't convert %s to JMAP", src_uid); r = IMAP_INTERNAL; goto done; } @@ -7726,7 +7770,8 @@ static int getcards_cb(void *rock, struct carddav_data *cdata) json_integer(record.size - record.header_size)); } - json_object_set_new(obj, "id", json_string(cdata->vcard_uid)); + json_object_set_new(obj, "id", + json_string(contactid_from_uid(cdata->vcard_uid))); json_object_set_new(obj, "addressBookId", json_string(strrchr(mbentry->name, '.')+1)); @@ -7824,7 +7869,7 @@ static void *card_filter_parse(json_t *arg) json_array_foreach(inCardGroup, i, val) { const char *id; if (json_unpack(val, "s", &id) != -1) { - hash_insert(id, (void*)1, f->inCardGroup); + hash_insert(contactid_to_uid(id), (void*)1, f->inCardGroup); } } } @@ -8340,7 +8385,10 @@ static int card_filter_match(void *vf, void *rock) /* XXX Calling carddav_db for every contact isn't really efficient. If * this turns out to be a performance issue, the carddav_db API might * support lookup contacts by group ids. */ - strarray_t *gids = carddav_getuid_groups(db, cdata->vcard_uid); + const char *uid = cdata->vcard_uid; + // carddav_writecard strips "urn:uuid:" from vcard_group members + if (!strncmp(uid, "urn:uuid:", 9)) uid += 9; + strarray_t *gids = carddav_getuid_groups(db, uid); if (!gids) { syslog(LOG_INFO, "carddav_getuid_groups(%s) returned NULL group array", @@ -8452,11 +8500,13 @@ static int _cardquery_cb(void *rock, struct carddav_data *cdata) if (!json_array_size(query->ids)) { query->result_position = query->total - 1; } - json_array_append_new(query->ids, json_string(cdata->vcard_uid)); + json_array_append_new(query->ids, + json_string(contactid_from_uid(cdata->vcard_uid))); } else { /* Keep matching entries for post-processing */ - json_object_set_new(entry, "id", json_string(cdata->vcard_uid)); + json_object_set_new(entry, "id", + json_string(contactid_from_uid(cdata->vcard_uid))); json_object_set_new(entry, "uid", json_string(cdata->vcard_uid)); ptrarray_append(&crock->entries, entry); entry = NULL; @@ -10818,7 +10868,8 @@ static int _jscard_to_vcard(struct jmap_req *req, if (cdata) { if (!strcmp(key, "id")) { - if (strcmpnull(cdata->vcard_uid, json_string_value(jval))) { + if (strcmpnull(json_string_value(jval), + contactid_from_uid(cdata->vcard_uid))) { jmap_parser_invalid(&parser, "id"); } continue; @@ -11265,8 +11316,10 @@ static int _card_set_create(jmap_req_t *req, static int maxattempts = 3; int i; for (i = 0; i < maxattempts; i++) { + struct buf buf = BUF_INITIALIZER; + buf_printf(&buf, "urn:uuid:%s", makeuuid()); free(uid); - uid = xstrdup(makeuuid()); + uid = buf_release(&buf); r = carddav_lookup_uid(db, uid, &mycdata); if (r == CYRUSDB_NOTFOUND) { json_object_set_new(item, "uid", json_string(uid)); @@ -11393,7 +11446,7 @@ static int _card_set_create(jmap_req_t *req, } r = 0; - json_object_set_new(item, "id", json_string(uid)); + json_object_set_new(item, "id", json_string(contactid_from_uid(uid))); struct index_record record; mailbox_find_index_record(*mailbox, (*mailbox)->i.last_uid, &record);