From 3373d7768e4efe55edcff1b18e16257a1ab761ac Mon Sep 17 00:00:00 2001 From: Ken Murchison Date: Thu, 2 Nov 2023 10:42:46 -0400 Subject: [PATCH] jmap_vacation.c: restore this to functional --- .../Cassandane/Cyrus/JMAPVacationResponse.pm | 212 ++++++++++++++++++ imap/jmap_vacation.c | 37 ++- 2 files changed, 237 insertions(+), 12 deletions(-) create mode 100644 cassandane/Cassandane/Cyrus/JMAPVacationResponse.pm diff --git a/cassandane/Cassandane/Cyrus/JMAPVacationResponse.pm b/cassandane/Cassandane/Cyrus/JMAPVacationResponse.pm new file mode 100644 index 0000000000..f734ec5f0e --- /dev/null +++ b/cassandane/Cassandane/Cyrus/JMAPVacationResponse.pm @@ -0,0 +1,212 @@ +#!/usr/bin/perl +# +# Copyright (c) 2011-2023 FastMail Pty Ltd. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# 3. The name "Fastmail Pty Ltd" must not be used to +# endorse or promote products derived from this software without +# prior written permission. For permission or any legal +# details, please contact +# FastMail Pty Ltd +# PO Box 234 +# Collins St West 8007 +# Victoria +# Australia +# +# 4. Redistributions of any form whatsoever must retain the following +# acknowledgment: +# "This product includes software developed by Fastmail Pty. Ltd." +# +# FASTMAIL PTY LTD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +# EVENT SHALL OPERA SOFTWARE AUSTRALIA BE LIABLE FOR ANY SPECIAL, INDIRECT +# OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF +# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +package Cassandane::Cyrus::JMAPVacationResponse; +use strict; +use warnings; +use DateTime; +use JSON; +use JSON::XS; +use Mail::JMAPTalk 0.13; +use Data::Dumper; +use Storable 'dclone'; +use File::Basename; +use IO::File; + +use lib '.'; +use base qw(Cassandane::Cyrus::TestCase); +use Cassandane::Util::Log; + +use charnames ':full'; + +sub new +{ + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj == 3 && $min == 0) { + # need to explicitly add 'body' to sieve_extensions for 3.0 + $config->set(sieve_extensions => + "fileinto reject vacation vacation-seconds imap4flags notify " . + "envelope relational regex subaddress copy date index " . + "imap4flags mailbox mboxmetadata servermetadata variables " . + "body"); + } + elsif ($maj < 3) { + # also for 2.5 (the earliest Cyrus that Cassandane can test) + $config->set(sieve_extensions => + "fileinto reject vacation vacation-seconds imap4flags notify " . + "envelope relational regex subaddress copy date index " . + "imap4flags body"); + } + + $config->set(caldav_realm => 'Cassandane', + conversations => 'yes', + httpmodules => 'jmap', + httpallowcompress => 'no'); + + return $class->SUPER::new({ + config => $config, + jmap => 1, + deliver => 1, + adminstore => 1, + services => [ 'imap', 'sieve', 'http' ] + }, @args); +} + +sub set_up +{ + my ($self) = @_; + $self->SUPER::set_up(); + $self->{jmap}->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:vacationresponse' + ]); +} + +sub tear_down +{ + my ($self) = @_; + $self->SUPER::tear_down(); +} + +sub test_vacation_get_none + :min_version_3_9 :needs_component_jmap +{ + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog "get vacation"; + my $res = $jmap->CallMethods([ + ['VacationResponse/get', { + properties => ['isEnabled'] + }, "R1"]]); + $self->assert_not_null($res); + $self->assert_str_equals('VacationResponse/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); + $self->assert_str_equals('singleton', $res->[0][1]{list}[0]{id}); + $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{isEnabled}); + $self->assert(not exists $res->[0][1]{list}[0]{subject}); +} + +sub test_vacation_set + :min_version_3_9 :needs_component_jmap +{ + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog "attempt to create a new vacation response"; + my $res = $jmap->CallMethods([ + ['VacationResponse/set', { + create => { + "1" => { + textBody => "Gone fishing" + } + } + }, "R1"]]); + $self->assert_not_null($res); + $self->assert_str_equals('VacationResponse/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_str_equals('singleton', $res->[0][1]{notCreated}{1}{type}); + + xlog "enable the vacation response"; + $res = $jmap->CallMethods([ + ['VacationResponse/set', { + update => { + "singleton" => { + isEnabled=> JSON::true, + textBody => "Gone fishing" + } + } + }, "R1"], + ['VacationResponse/get', { + }, "R2"] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('VacationResponse/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{singleton}); + $self->assert_str_equals('VacationResponse/get', $res->[1][0]); + $self->assert_str_equals('R2', $res->[1][2]); + $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); + $self->assert_str_equals('singleton', $res->[1][1]{list}[0]{id}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isEnabled}); + $self->assert_str_equals('Gone fishing', $res->[1][1]{list}[0]{textBody}); + + xlog "disable the vacation response"; + $res = $jmap->CallMethods([ + ['VacationResponse/set', { + update => { + "singleton" => { + isEnabled=> JSON::false + } + } + }, "R1"], + ['VacationResponse/get', { + }, "R2"] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('VacationResponse/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{singleton}); + $self->assert_str_equals('VacationResponse/get', $res->[1][0]); + $self->assert_str_equals('R2', $res->[1][2]); + $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); + $self->assert_str_equals('singleton', $res->[1][1]{list}[0]{id}); + $self->assert_equals(JSON::false, $res->[1][1]{list}[0]{isEnabled}); + $self->assert_str_equals('Gone fishing', $res->[1][1]{list}[0]{textBody}); + + xlog "attempt to destroy the vacation response"; + $res = $jmap->CallMethods([ + ['VacationResponse/set', { + destroy => ["singleton"] + }, "R1"]]); + $self->assert_not_null($res); + $self->assert_str_equals('VacationResponse/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_str_equals('singleton', + $res->[0][1]{notDestroyed}{singleton}{type}); +} + +1; diff --git a/imap/jmap_vacation.c b/imap/jmap_vacation.c index 951eb234e6..91048ed385 100644 --- a/imap/jmap_vacation.c +++ b/imap/jmap_vacation.c @@ -99,6 +99,19 @@ HIDDEN void jmap_vacation_init(jmap_settings_t *settings) { if (!config_getswitch(IMAPOPT_JMAP_VACATION)) return; + if (config_getswitch(IMAPOPT_SIEVEUSEHOMEDIR)) { + xsyslog(LOG_WARNING, + "can't use home directories -- disabling module", NULL); + return; + } + + if (!sievedir_valid_path(config_getstring(IMAPOPT_SIEVEDIR))) { + xsyslog(LOG_WARNING, + "sievedir option is not defined or invalid -- disabling module", + NULL); + return; + } + #ifdef USE_SIEVE unsigned long config_ext = config_getbitfield(IMAPOPT_SIEVE_EXTENSIONS); unsigned long required = @@ -187,19 +200,14 @@ static const jmap_property_t vacation_props[] = { " because the active Sieve script does not" \ " properly include the '" JMAP_URN_VACATION "' script." -static json_t *vacation_read(jmap_req_t *req, +static json_t *vacation_read(jmap_req_t *req, struct mailbox *mailbox, struct sieve_data *sdata, unsigned *status) { const char *sieve_dir = user_sieve_path(req->accountid); - struct mailbox *mailbox = NULL; struct buf content = BUF_INITIALIZER; json_t *vacation = NULL; - int r = jmap_openmbox(req, sdata->mailbox, &mailbox, 0); - if (!r) { - r = sieve_script_fetch(mailbox, sdata, &content); - jmap_closembox(req, &mailbox); - } + sieve_script_fetch(mailbox, sdata, &content); /* Parse JMAP from vacation script */ if (buf_len(&content)) { @@ -272,11 +280,11 @@ static json_t *vacation_read(jmap_req_t *req, return vacation; } -static void vacation_get(jmap_req_t *req, +static void vacation_get(jmap_req_t *req, struct mailbox *mailbox, struct sieve_data *sdata, struct jmap_get *get) { /* Read script */ - json_t *vacation = vacation_read(req, sdata, NULL); + json_t *vacation = vacation_read(req, mailbox, sdata, NULL); /* Strip unwanted properties */ if (!jmap_wantprop(get->props, "isEnabled")) @@ -314,6 +322,11 @@ static int jmap_vacation_get(jmap_req_t *req) goto done; } + r = sieve_ensure_folder(req->accountid, &mailbox); + if (r) goto done; + + mailbox_unlock_index(mailbox, NULL); + db = sievedb_open_userid(req->accountid); if (!db) { r = IMAP_INTERNAL; @@ -338,12 +351,12 @@ static int jmap_vacation_get(jmap_req_t *req) const char *id = json_string_value(jval); if (!strcmp(id, "singleton")) - vacation_get(req, sdata, &get); + vacation_get(req, mailbox, sdata, &get); else json_array_append(get.not_found, jval); } } - else vacation_get(req, sdata, &get); + else vacation_get(req, mailbox, sdata, &get); /* Build response */ struct buf buf = BUF_INITIALIZER; @@ -375,7 +388,7 @@ static void vacation_update(struct jmap_req *req, const char *err = NULL; int r; - vacation = vacation_read(req, sdata, &status); + vacation = vacation_read(req, mailbox, sdata, &status); prop = json_object_get(patch, "isEnabled"); if (!json_is_boolean(prop))