Skip to content

Commit

Permalink
Merge pull request #4987 from rsto/jmap_copy_preserve_xprops
Browse files Browse the repository at this point in the history
JMAP Contacts: preserve x-properties in Contact/copy
  • Loading branch information
rsto committed Jul 30, 2024
2 parents 59fef69 + b0694cc commit e353858
Show file tree
Hide file tree
Showing 5 changed files with 342 additions and 2 deletions.
101 changes: 101 additions & 0 deletions cassandane/tiny-tests/JMAPContacts/contact_copy_preserve_xprops
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!perl
use Cassandane::Tiny;

sub test_contact_copy_preserve_xprops
: needs_component_jmap {
my ($self) = @_;
my $jmap = $self->{jmap};
my $carddav = $self->{carddav};
my $admintalk = $self->{adminstore}->get_client();
my $service = $self->{instance}->get_service("http");

xlog $self, "create shared account";
$admintalk->create("user.other");

my $otherCarddav = Net::CardDAVTalk->new(
user => "other",
password => 'pass',
host => $service->host(),
port => $service->port(),
scheme => 'http',
url => '/',
expandurl => 1,
);

my $otherJmap = Mail::JMAPTalk->new(
user => 'other',
password => 'pass',
host => $service->host(),
port => $service->port(),
scheme => 'http',
url => '/jmap/',
);
$otherJmap->DefaultUsing([
'urn:ietf:params:jmap:core',
'https://cyrusimap.org/ns/jmap/contacts',
'https://cyrusimap.org/ns/jmap/debug'
]);

xlog $self, "share addressbook";
$admintalk->setacl(
"user.other.#addressbooks.Default",
"cassandane" => 'lrswipkxtecdn'
) or die;

my $card = decode(
'utf-8', <<EOF
BEGIN:VCARD
VERSION:3.0
UID:0A8F88DE-1073-4D47-926F-0D535523FD15
N:Smith;Hank;;
FN:Hank Smith
X-FOO;X-BAZ=Bam:Bar
REV:2008-04-24T19:52:43Z
END:VCARD
EOF
);
$card =~ s/\r?\n/\r\n/gs;
$carddav->Request('PUT', 'Default/test.vcf', $card, 'Content-Type' => 'text/vcard');

my $res = $jmap->CallMethods([
[ 'Contact/query', {}, 'R1' ],
]);
$self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} });
my $contactId = $res->[0][1]{ids}[0];
$self->assert_not_null($contactId);

$res = $jmap->CallMethods([
[
'Contact/copy',
{
fromAccountId => 'cassandane',
accountId => 'other',
create => {
contact1 => {
addressbookId => 'Default',
id => $contactId
}
},
onSuccessDestroyOriginal => JSON::false,
},
'R1'
],
]);
my $copiedContactId = $res->[0][1]{created}{contact1}{id};
$self->assert_not_null($copiedContactId);

$res = $otherJmap->CallMethods([
[
'Contact/get',
{
accountId => 'other',
ids => [$copiedContactId],
properties => [ 'x-href' ],
},
'R1'
],
]);

$card = $otherCarddav->Request('GET', $res->[0][1]{list}[0]{'x-href'});
$self->assert_matches(qr/^X-FOO;X-BAZ=Bam:Bar\r$/m, $card->{content});
}
74 changes: 74 additions & 0 deletions cassandane/tiny-tests/JMAPContacts/contact_set_preserve_xprops
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!perl
use Cassandane::Tiny;
use Encode qw(decode);

sub test_contact_set_preserve_xprops
: needs_component_jmap {
my ($self) = @_;
my $jmap = $self->{jmap};
my $carddav = $self->{carddav};

xlog $self, "Create vCard with x-property";
my $card = decode(
'utf-8', <<EOF
BEGIN:VCARD
VERSION:3.0
UID:0A8F88DE-1073-4D47-926F-0D535523FD15
N:Smith;Hank;;
FN:Hank Smith
X-FOO;X-BAZ=Bam:Bar
REV:2008-04-24T19:52:43Z
END:VCARD
EOF
);
$card =~ s/\r?\n/\r\n/gs;
$carddav->Request('PUT', 'Default/test.vcf', $card, 'Content-Type' => 'text/vcard');

xlog $self, "Update some contact property";
my $res = $jmap->CallMethods([
[ 'Contact/query', {}, 'R1' ],
[
'Contact/get',
{
'#ids' => {
resultOf => 'R1',
path => '/ids',
name => 'Contact/query',
},
properties => ['lastName'],
},
'R2'
],
]);

my $contactId = $res->[0][1]{ids}[0];
$self->assert_not_null($contactId);
$self->assert_str_equals('Smith', $res->[1][1]{list}[0]{lastName});

$res = $jmap->CallMethods([
[
'Contact/set',
{
update => {
$contactId => {
lastName => 'Kraut',
}
},
},
'R1'
],
[
'Contact/get',
{
ids => [$contactId],
properties => ['lastName'],
},
'R2'
],
]);
$self->assert_str_equals('Kraut', $res->[1][1]{list}[0]{lastName});

xlog $self, "Update x-property is preserved in vCard";
$res = $carddav->Request('GET', 'Default/test.vcf');
$self->assert_matches(qr/^X-FOO;X-BAZ=Bam:Bar\r?$/m, $res->{content});
}
5 changes: 4 additions & 1 deletion imap/jmap_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -1594,7 +1594,7 @@ HIDDEN void jmap_get_parse(jmap_req_t *req,
propdef = NULL;
}
}
if (!propdef) {
if (!propdef || (propdef->flags & JMAP_PROP_REJECT_GET)) {
jmap_parser_push_index(parser, "properties", i, name);
jmap_parser_invalid(parser, NULL);
jmap_parser_pop(parser);
Expand Down Expand Up @@ -1706,6 +1706,9 @@ static void jmap_set_validate_props(jmap_req_t *req, const char *id, json_t *job
else if (prop->capability && !jmap_is_using(req, prop->capability)) {
json_array_append_new(invalid, json_string(path));
}
else if (prop->flags & JMAP_PROP_REJECT_SET) {
json_array_append_new(invalid, json_string(path));
}
else if (id) {
/* update */
if (!strcmp("id", prop->name) &&
Expand Down
4 changes: 3 additions & 1 deletion imap/jmap_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,9 @@ enum {
JMAP_PROP_SERVER_SET = (1<<0),
JMAP_PROP_IMMUTABLE = (1<<1),
JMAP_PROP_SKIP_GET = (1<<2), // skip in Foo/get if not requested by client
JMAP_PROP_ALWAYS_GET = (1<<3) // always include in Foo/get
JMAP_PROP_ALWAYS_GET = (1<<3), // always include in Foo/get
JMAP_PROP_REJECT_GET = (1<<4), // reject as unknown in Foo/get
JMAP_PROP_REJECT_SET = (1<<5) // reject as unknown in Foo/set
};

extern const jmap_property_t *jmap_property_find(const char *name,
Expand Down
Loading

0 comments on commit e353858

Please sign in to comment.