Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117751789
caldav_db.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
35 KB
Referenced Files
None
Subscribers
None
caldav_db.c
View Options
/* caldav_db.c -- implementation of per-user CalDAV database
*
* Copyright (c) 1994-2012 Carnegie Mellon University. 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 "Carnegie Mellon University" 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
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY 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.
*
*/
#include
<config.h>
#include
<sysexits.h>
#include
<syslog.h>
#include
<string.h>
#include
<libical/ical.h>
#include
"caldav_alarm.h"
#include
"caldav_db.h"
#include
"cyrusdb.h"
#include
"httpd.h"
#include
"http_dav.h"
#include
"ical_support.h"
#include
"libconfig.h"
#include
"mboxname.h"
#include
"util.h"
#include
"xstrlcat.h"
#include
"xmalloc.h"
/* generated headers are not necessarily in current directory */
#include
"imap/imap_err.h"
struct
caldav_db
{
sqldb_t
*
db
;
/* DB handle */
char
*
sched_inbox
;
/* DB owner's scheduling Inbox */
struct
buf
mailbox
;
/* buffers for copies of column text */
struct
buf
resource
;
struct
buf
lock_token
;
struct
buf
lock_owner
;
struct
buf
lock_ownerid
;
struct
buf
ical_uid
;
struct
buf
organizer
;
struct
buf
dtstart
;
struct
buf
dtend
;
struct
buf
sched_tag
;
struct
buf
jmapdata
;
};
static
struct
namespace
caldav_namespace
;
EXPORTED
time_t
caldav_epoch
=
-1
;
EXPORTED
time_t
caldav_eternity
=
-1
;
static
int
caldav_initialized
=
0
;
static
void
done_cb
(
void
*
rock
__attribute__
((
unused
)))
{
caldav_done
();
}
static
void
init_internal
()
{
if
(
!
caldav_initialized
)
{
caldav_init
();
cyrus_modules_add
(
done_cb
,
NULL
);
}
}
EXPORTED
int
caldav_init
(
void
)
{
int
r
;
struct
icaltimetype
date
;
/* Set namespace -- force standard (internal) */
if
((
r
=
mboxname_init_namespace
(
&
caldav_namespace
,
1
)))
{
syslog
(
LOG_ERR
,
"%s"
,
error_message
(
r
));
fatal
(
error_message
(
r
),
EX_CONFIG
);
}
/* Get min date-time */
date
=
icaltime_from_string
(
config_getstring
(
IMAPOPT_CALDAV_MINDATETIME
));
if
(
!
icaltime_is_null_time
(
date
))
{
caldav_epoch
=
icaltime_as_timet_with_zone
(
date
,
NULL
);
}
if
(
caldav_epoch
==
-1
)
caldav_epoch
=
INT_MIN
;
/* Get max date-time */
date
=
icaltime_from_string
(
config_getstring
(
IMAPOPT_CALDAV_MAXDATETIME
));
if
(
!
icaltime_is_null_time
(
date
))
{
caldav_eternity
=
icaltime_as_timet_with_zone
(
date
,
NULL
);
}
if
(
caldav_eternity
==
-1
)
caldav_eternity
=
INT_MAX
;
r
=
sqldb_init
();
caldav_alarm_init
();
if
(
!
r
)
caldav_initialized
=
1
;
return
r
;
}
EXPORTED
int
caldav_done
(
void
)
{
int
r
;
caldav_alarm_done
();
r
=
sqldb_done
();
if
(
!
r
)
caldav_initialized
=
0
;
return
r
;
}
EXPORTED
struct
caldav_db
*
caldav_open_userid
(
const
char
*
userid
)
{
struct
caldav_db
*
caldavdb
=
NULL
;
init_internal
();
sqldb_t
*
db
=
dav_open_userid
(
userid
);
if
(
!
db
)
return
NULL
;
caldavdb
=
xzmalloc
(
sizeof
(
struct
caldav_db
));
caldavdb
->
db
=
db
;
/* Construct mbox name corresponding to userid's scheduling Inbox */
caldavdb
->
sched_inbox
=
caldav_mboxname
(
userid
,
SCHED_INBOX
);
return
caldavdb
;
}
/* Open DAV DB corresponding to userid */
EXPORTED
struct
caldav_db
*
caldav_open_mailbox
(
struct
mailbox
*
mailbox
)
{
struct
caldav_db
*
caldavdb
=
NULL
;
char
*
userid
=
mboxname_to_userid
(
mailbox
->
name
);
init_internal
();
if
(
userid
)
{
caldavdb
=
caldav_open_userid
(
userid
);
free
(
userid
);
return
caldavdb
;
}
sqldb_t
*
db
=
dav_open_mailbox
(
mailbox
);
if
(
!
db
)
return
NULL
;
caldavdb
=
xzmalloc
(
sizeof
(
struct
caldav_db
));
caldavdb
->
db
=
db
;
return
caldavdb
;
}
/* Close DAV DB */
EXPORTED
int
caldav_close
(
struct
caldav_db
*
caldavdb
)
{
int
r
=
0
;
if
(
!
caldavdb
)
return
0
;
free
(
caldavdb
->
sched_inbox
);
buf_free
(
&
caldavdb
->
mailbox
);
buf_free
(
&
caldavdb
->
resource
);
buf_free
(
&
caldavdb
->
lock_token
);
buf_free
(
&
caldavdb
->
lock_owner
);
buf_free
(
&
caldavdb
->
lock_ownerid
);
buf_free
(
&
caldavdb
->
ical_uid
);
buf_free
(
&
caldavdb
->
organizer
);
buf_free
(
&
caldavdb
->
dtstart
);
buf_free
(
&
caldavdb
->
dtend
);
buf_free
(
&
caldavdb
->
sched_tag
);
buf_free
(
&
caldavdb
->
jmapdata
);
r
=
dav_close
(
&
caldavdb
->
db
);
free
(
caldavdb
);
return
r
;
}
EXPORTED
int
caldav_begin
(
struct
caldav_db
*
caldavdb
)
{
return
sqldb_begin
(
caldavdb
->
db
,
"caldav"
);
}
EXPORTED
int
caldav_commit
(
struct
caldav_db
*
caldavdb
)
{
return
sqldb_commit
(
caldavdb
->
db
,
"caldav"
);
}
EXPORTED
int
caldav_abort
(
struct
caldav_db
*
caldavdb
)
{
return
sqldb_rollback
(
caldavdb
->
db
,
"caldav"
);
}
#define RROCK_FLAG_TOMBSTONES (1<<0)
struct
read_rock
{
struct
caldav_db
*
db
;
struct
caldav_data
*
cdata
;
int
flags
;
caldav_cb_t
*
cb
;
void
*
rock
;
};
static
const
char
*
column_text_to_buf
(
const
char
*
text
,
struct
buf
*
buf
)
{
if
(
text
)
{
buf_setcstr
(
buf
,
text
);
text
=
buf_cstring
(
buf
);
}
return
text
;
}
static
void
_num_to_comp_flags
(
struct
comp_flags
*
flags
,
unsigned
num
)
{
flags
->
recurring
=
num
&
1
;
flags
->
transp
=
(
num
>>
1
)
&
1
;
flags
->
status
=
(
num
>>
2
)
&
3
;
flags
->
tzbyref
=
(
num
>>
4
)
&
1
;
flags
->
mattach
=
(
num
>>
5
)
&
1
;
flags
->
shared
=
(
num
>>
6
)
&
1
;
}
static
unsigned
_comp_flags_to_num
(
struct
comp_flags
*
flags
)
{
return
(
flags
->
recurring
&
1
)
+
((
flags
->
transp
&
1
)
<<
1
)
+
((
flags
->
status
&
3
)
<<
2
)
+
((
flags
->
tzbyref
&
1
)
<<
4
)
+
((
flags
->
mattach
&
1
)
<<
5
)
+
((
flags
->
shared
&
1
)
<<
6
);
}
#define CMD_READFIELDS \
"SELECT rowid, creationdate, mailbox, resource, imap_uid," \
" lock_token, lock_owner, lock_ownerid, lock_expire," \
" comp_type, ical_uid, organizer, dtstart, dtend," \
" comp_flags, sched_tag, alive, modseq, createdmodseq," \
" NULL, NULL" \
" FROM ical_objs" \
#define CMD_READFIELDS_JMAP \
"SELECT ical_objs.rowid, creationdate, mailbox, resource, imap_uid,"\
" lock_token, lock_owner, lock_ownerid, lock_expire," \
" comp_type, ical_uid, organizer, dtstart, dtend," \
" comp_flags, sched_tag, alive, modseq, createdmodseq," \
" jmapversion, jmapdata" \
" FROM ical_objs LEFT JOIN ical_jmapcache" \
" ON (ical_objs.rowid = ical_jmapcache.rowid AND ical_jmapcache.userid = :asuserid)"
static
int
read_cb
(
sqlite3_stmt
*
stmt
,
void
*
rock
)
{
struct
read_rock
*
rrock
=
(
struct
read_rock
*
)
rock
;
struct
caldav_db
*
db
=
rrock
->
db
;
struct
caldav_data
*
cdata
=
rrock
->
cdata
;
int
r
=
0
;
memset
(
cdata
,
0
,
sizeof
(
struct
caldav_data
));
cdata
->
dav
.
alive
=
sqlite3_column_int
(
stmt
,
16
);
cdata
->
dav
.
modseq
=
sqlite3_column_int64
(
stmt
,
17
);
cdata
->
dav
.
createdmodseq
=
sqlite3_column_int64
(
stmt
,
18
);
if
(
!
(
rrock
->
flags
&
RROCK_FLAG_TOMBSTONES
)
&&
!
cdata
->
dav
.
alive
)
return
0
;
cdata
->
dav
.
rowid
=
sqlite3_column_int
(
stmt
,
0
);
cdata
->
dav
.
creationdate
=
sqlite3_column_int
(
stmt
,
1
);
cdata
->
dav
.
imap_uid
=
sqlite3_column_int
(
stmt
,
4
);
cdata
->
dav
.
lock_expire
=
sqlite3_column_int
(
stmt
,
8
);
cdata
->
comp_type
=
sqlite3_column_int
(
stmt
,
9
);
_num_to_comp_flags
(
&
cdata
->
comp_flags
,
sqlite3_column_int
(
stmt
,
14
));
cdata
->
jmapversion
=
sqlite3_column_int
(
stmt
,
19
);
if
(
rrock
->
cb
)
{
/* We can use the column data directly for the callback */
cdata
->
dav
.
mailbox
=
(
const
char
*
)
sqlite3_column_text
(
stmt
,
2
);
cdata
->
dav
.
resource
=
(
const
char
*
)
sqlite3_column_text
(
stmt
,
3
);
cdata
->
dav
.
lock_token
=
(
const
char
*
)
sqlite3_column_text
(
stmt
,
5
);
cdata
->
dav
.
lock_owner
=
(
const
char
*
)
sqlite3_column_text
(
stmt
,
6
);
cdata
->
dav
.
lock_ownerid
=
(
const
char
*
)
sqlite3_column_text
(
stmt
,
7
);
cdata
->
ical_uid
=
(
const
char
*
)
sqlite3_column_text
(
stmt
,
10
);
cdata
->
organizer
=
(
const
char
*
)
sqlite3_column_text
(
stmt
,
11
);
cdata
->
dtstart
=
(
const
char
*
)
sqlite3_column_text
(
stmt
,
12
);
cdata
->
dtend
=
(
const
char
*
)
sqlite3_column_text
(
stmt
,
13
);
cdata
->
sched_tag
=
(
const
char
*
)
sqlite3_column_text
(
stmt
,
15
);
cdata
->
jmapdata
=
(
const
char
*
)
sqlite3_column_text
(
stmt
,
20
);
r
=
rrock
->
cb
(
rrock
->
rock
,
cdata
);
}
else
{
/* For single row SELECTs like caldav_read(),
* we need to make a copy of the column data before
* it gets flushed by sqlite3_step() or sqlite3_reset() */
cdata
->
dav
.
mailbox
=
column_text_to_buf
((
const
char
*
)
sqlite3_column_text
(
stmt
,
2
),
&
db
->
mailbox
);
cdata
->
dav
.
resource
=
column_text_to_buf
((
const
char
*
)
sqlite3_column_text
(
stmt
,
3
),
&
db
->
resource
);
cdata
->
dav
.
lock_token
=
column_text_to_buf
((
const
char
*
)
sqlite3_column_text
(
stmt
,
5
),
&
db
->
lock_token
);
cdata
->
dav
.
lock_owner
=
column_text_to_buf
((
const
char
*
)
sqlite3_column_text
(
stmt
,
6
),
&
db
->
lock_owner
);
cdata
->
dav
.
lock_ownerid
=
column_text_to_buf
((
const
char
*
)
sqlite3_column_text
(
stmt
,
7
),
&
db
->
lock_ownerid
);
cdata
->
ical_uid
=
column_text_to_buf
((
const
char
*
)
sqlite3_column_text
(
stmt
,
10
),
&
db
->
ical_uid
);
cdata
->
organizer
=
column_text_to_buf
((
const
char
*
)
sqlite3_column_text
(
stmt
,
11
),
&
db
->
organizer
);
cdata
->
dtstart
=
column_text_to_buf
((
const
char
*
)
sqlite3_column_text
(
stmt
,
12
),
&
db
->
dtstart
);
cdata
->
dtend
=
column_text_to_buf
((
const
char
*
)
sqlite3_column_text
(
stmt
,
13
),
&
db
->
dtend
);
cdata
->
sched_tag
=
column_text_to_buf
((
const
char
*
)
sqlite3_column_text
(
stmt
,
15
),
&
db
->
sched_tag
);
cdata
->
jmapdata
=
column_text_to_buf
((
const
char
*
)
sqlite3_column_text
(
stmt
,
20
),
&
db
->
jmapdata
);
}
return
r
;
}
#define CMD_SELRSRC CMD_READFIELDS \
" WHERE mailbox = :mailbox AND resource = :resource;"
EXPORTED
int
caldav_lookup_resource
(
struct
caldav_db
*
caldavdb
,
const
char
*
mailbox
,
const
char
*
resource
,
struct
caldav_data
**
result
,
int
tombstones
)
{
struct
sqldb_bindval
bval
[]
=
{
{
":mailbox"
,
SQLITE_TEXT
,
{
.
s
=
mailbox
}
},
{
":resource"
,
SQLITE_TEXT
,
{
.
s
=
resource
}
},
{
NULL
,
SQLITE_NULL
,
{
.
s
=
NULL
}
}
};
static
struct
caldav_data
cdata
;
struct
read_rock
rrock
=
{
caldavdb
,
&
cdata
,
tombstones
,
NULL
,
NULL
};
int
r
;
*
result
=
memset
(
&
cdata
,
0
,
sizeof
(
struct
caldav_data
));
r
=
sqldb_exec
(
caldavdb
->
db
,
CMD_SELRSRC
,
bval
,
&
read_cb
,
&
rrock
);
if
(
!
r
&&
!
cdata
.
dav
.
rowid
)
r
=
CYRUSDB_NOTFOUND
;
/* always add the mailbox and resource, so error responses don't
* crash out */
cdata
.
dav
.
mailbox
=
mailbox
;
cdata
.
dav
.
resource
=
resource
;
return
r
;
}
#define CMD_SELIMAPUID CMD_READFIELDS \
" WHERE mailbox = :mailbox AND imap_uid = :imap_uid;"
EXPORTED
int
caldav_lookup_imapuid
(
struct
caldav_db
*
caldavdb
,
const
char
*
mailbox
,
int
imap_uid
,
struct
caldav_data
**
result
,
int
tombstones
)
{
struct
sqldb_bindval
bval
[]
=
{
{
":mailbox"
,
SQLITE_TEXT
,
{
.
s
=
mailbox
}
},
{
":imap_uid"
,
SQLITE_INTEGER
,
{
.
i
=
imap_uid
}
},
{
NULL
,
SQLITE_NULL
,
{
.
s
=
NULL
}
}
};
static
struct
caldav_data
cdata
;
struct
read_rock
rrock
=
{
caldavdb
,
&
cdata
,
tombstones
,
NULL
,
NULL
};
int
r
;
*
result
=
memset
(
&
cdata
,
0
,
sizeof
(
struct
caldav_data
));
r
=
sqldb_exec
(
caldavdb
->
db
,
CMD_SELIMAPUID
,
bval
,
&
read_cb
,
&
rrock
);
if
(
!
r
&&
!
cdata
.
dav
.
rowid
)
r
=
CYRUSDB_NOTFOUND
;
cdata
.
dav
.
mailbox
=
mailbox
;
cdata
.
dav
.
imap_uid
=
imap_uid
;
return
r
;
}
#define CMD_SELUID CMD_READFIELDS \
" WHERE ical_uid = :ical_uid AND mailbox != :inbox AND alive = 1;"
EXPORTED
int
caldav_lookup_uid
(
struct
caldav_db
*
caldavdb
,
const
char
*
ical_uid
,
struct
caldav_data
**
result
)
{
struct
sqldb_bindval
bval
[]
=
{
{
":ical_uid"
,
SQLITE_TEXT
,
{
.
s
=
ical_uid
}
},
{
":inbox"
,
SQLITE_TEXT
,
{
.
s
=
caldavdb
->
sched_inbox
}
},
{
NULL
,
SQLITE_NULL
,
{
.
s
=
NULL
}
}
};
static
struct
caldav_data
cdata
;
struct
read_rock
rrock
=
{
caldavdb
,
&
cdata
,
0
,
NULL
,
NULL
};
int
r
;
/* XXX - ability to pass through the tombstones flag */
*
result
=
memset
(
&
cdata
,
0
,
sizeof
(
struct
caldav_data
));
r
=
sqldb_exec
(
caldavdb
->
db
,
CMD_SELUID
,
bval
,
&
read_cb
,
&
rrock
);
if
(
!
r
&&
!
cdata
.
dav
.
rowid
)
r
=
CYRUSDB_NOTFOUND
;
return
r
;
}
#define CMD_SELMBOX CMD_READFIELDS \
" WHERE mailbox = :mailbox AND alive = 1;"
#define CMD_SELALIVE CMD_READFIELDS \
" WHERE alive = 1;"
EXPORTED
int
caldav_foreach
(
struct
caldav_db
*
caldavdb
,
const
char
*
mailbox
,
caldav_cb_t
*
cb
,
void
*
rock
)
{
struct
sqldb_bindval
bval
[]
=
{
{
":mailbox"
,
SQLITE_TEXT
,
{
.
s
=
mailbox
}
},
{
NULL
,
SQLITE_NULL
,
{
.
s
=
NULL
}
}
};
struct
caldav_data
cdata
;
struct
read_rock
rrock
=
{
caldavdb
,
&
cdata
,
0
,
cb
,
rock
};
/* XXX - tombstones */
if
(
mailbox
)
{
return
sqldb_exec
(
caldavdb
->
db
,
CMD_SELMBOX
,
bval
,
&
read_cb
,
&
rrock
);
}
else
{
return
sqldb_exec
(
caldavdb
->
db
,
CMD_SELALIVE
,
bval
,
&
read_cb
,
&
rrock
);
}
}
#define CMD_SELRANGE_MBOX CMD_READFIELDS \
" WHERE dtend > :after AND dtstart < :before " \
" AND mailbox = :mailbox AND alive = 1 "
#define CMD_SELRANGE CMD_READFIELDS \
" WHERE dtend > :after AND dtstart < :before " \
" AND alive = 1 "
EXPORTED
int
caldav_foreach_timerange
(
struct
caldav_db
*
caldavdb
,
const
char
*
mailbox
,
time_t
after
,
time_t
before
,
enum
caldav_sort
*
sort
,
size_t
nsort
,
caldav_cb_t
*
cb
,
void
*
rock
)
{
struct
sqldb_bindval
bval
[]
=
{
{
":after"
,
SQLITE_TEXT
,
{
.
s
=
NULL
}
},
{
":before"
,
SQLITE_TEXT
,
{
.
s
=
NULL
}
},
{
":mailbox"
,
SQLITE_TEXT
,
{
.
s
=
mailbox
}
},
{
NULL
,
SQLITE_NULL
,
{
.
s
=
NULL
}
}
};
struct
caldav_data
cdata
;
struct
read_rock
rrock
=
{
caldavdb
,
&
cdata
,
0
,
cb
,
rock
};
icaltimetype
dtafter
,
dtbefore
;
icaltimezone
*
utc
=
icaltimezone_get_utc_timezone
();
dtafter
=
icaltime_from_timet_with_zone
(
after
,
0
,
utc
);
dtbefore
=
icaltime_from_timet_with_zone
(
before
,
0
,
utc
);
bval
[
0
].
val
.
s
=
icaltime_as_ical_string
(
dtafter
);
bval
[
1
].
val
.
s
=
icaltime_as_ical_string
(
dtbefore
);
/* XXX - if 'before' defines the zero second of a day, a full-day
* event starting on that day matches. That's not entirely correct,
* since 'before' is defined to be exclusive. */
/* XXX - tombstones */
struct
buf
stmt
=
BUF_INITIALIZER
;
buf_setcstr
(
&
stmt
,
mailbox
?
CMD_SELRANGE_MBOX
:
CMD_SELRANGE
);
if
(
nsort
)
{
buf_appendcstr
(
&
stmt
,
" ORDER BY "
);
size_t
i
;
for
(
i
=
0
;
i
<
nsort
;
i
++
)
{
if
(
i
)
buf_appendcstr
(
&
stmt
,
", "
);
switch
(
sort
[
i
]
&
~
CAL_SORT_DESC
)
{
case
CAL_SORT_UID
:
buf_appendcstr
(
&
stmt
,
"ical_uid"
);
break
;
case
CAL_SORT_START
:
buf_appendcstr
(
&
stmt
,
"dtstart"
);
break
;
case
CAL_SORT_MAILBOX
:
buf_appendcstr
(
&
stmt
,
"mailbox"
);
break
;
default
:
continue
;
}
buf_appendcstr
(
&
stmt
,
sort
[
i
]
&
CAL_SORT_DESC
?
" DESC"
:
" ASC"
);
}
}
buf_putc
(
&
stmt
,
';'
);
int
r
=
sqldb_exec
(
caldavdb
->
db
,
buf_cstring
(
&
stmt
),
bval
,
&
read_cb
,
&
rrock
);
buf_free
(
&
stmt
);
return
r
;
}
#define CMD_INSERT \
"INSERT INTO ical_objs (" \
" alive, mailbox, resource, creationdate, imap_uid, modseq," \
" createdmodseq," \
" lock_token, lock_owner, lock_ownerid, lock_expire," \
" comp_type, ical_uid, organizer, dtstart, dtend," \
" comp_flags, sched_tag )" \
" VALUES (" \
" :alive, :mailbox, :resource, :creationdate, :imap_uid, :modseq," \
" :createdmodseq," \
" :lock_token, :lock_owner, :lock_ownerid, :lock_expire," \
" :comp_type, :ical_uid, :organizer, :dtstart, :dtend," \
" :comp_flags, :sched_tag );"
#define CMD_UPDATE \
"UPDATE ical_objs SET" \
" alive = :alive," \
" creationdate = :creationdate," \
" imap_uid = :imap_uid," \
" modseq = :modseq," \
" createdmodseq = :createdmodseq," \
" lock_token = :lock_token," \
" lock_owner = :lock_owner," \
" lock_ownerid = :lock_ownerid," \
" lock_expire = :lock_expire," \
" comp_type = :comp_type," \
" ical_uid = :ical_uid," \
" organizer = :organizer," \
" dtstart = :dtstart," \
" dtend = :dtend," \
" comp_flags = :comp_flags," \
" sched_tag = :sched_tag" \
" WHERE rowid = :rowid;"
#define CMD_DELETE_JMAPCACHE "DELETE FROM ical_jmapcache WHERE rowid = :rowid"
EXPORTED
int
caldav_write
(
struct
caldav_db
*
caldavdb
,
struct
caldav_data
*
cdata
)
{
unsigned
comp_flags
=
_comp_flags_to_num
(
&
cdata
->
comp_flags
);
struct
sqldb_bindval
bval
[]
=
{
{
":rowid"
,
SQLITE_INTEGER
,
{
.
i
=
cdata
->
dav
.
rowid
}
},
{
":alive"
,
SQLITE_INTEGER
,
{
.
i
=
cdata
->
dav
.
alive
}
},
{
":mailbox"
,
SQLITE_TEXT
,
{
.
s
=
cdata
->
dav
.
mailbox
}
},
{
":resource"
,
SQLITE_TEXT
,
{
.
s
=
cdata
->
dav
.
resource
}
},
{
":creationdate"
,
SQLITE_INTEGER
,
{
.
i
=
cdata
->
dav
.
creationdate
}
},
{
":imap_uid"
,
SQLITE_INTEGER
,
{
.
i
=
cdata
->
dav
.
imap_uid
}
},
{
":modseq"
,
SQLITE_INTEGER
,
{
.
i
=
cdata
->
dav
.
modseq
}
},
{
":createdmodseq"
,
SQLITE_INTEGER
,
{
.
i
=
cdata
->
dav
.
createdmodseq
}
},
{
":lock_token"
,
SQLITE_TEXT
,
{
.
s
=
cdata
->
dav
.
lock_token
}
},
{
":lock_owner"
,
SQLITE_TEXT
,
{
.
s
=
cdata
->
dav
.
lock_owner
}
},
{
":lock_ownerid"
,
SQLITE_TEXT
,
{
.
s
=
cdata
->
dav
.
lock_ownerid
}
},
{
":lock_expire"
,
SQLITE_INTEGER
,
{
.
i
=
cdata
->
dav
.
lock_expire
}
},
{
":comp_type"
,
SQLITE_INTEGER
,
{
.
i
=
cdata
->
comp_type
}
},
{
":ical_uid"
,
SQLITE_TEXT
,
{
.
s
=
cdata
->
ical_uid
}
},
{
":organizer"
,
SQLITE_TEXT
,
{
.
s
=
cdata
->
organizer
}
},
{
":dtstart"
,
SQLITE_TEXT
,
{
.
s
=
cdata
->
dtstart
}
},
{
":dtend"
,
SQLITE_TEXT
,
{
.
s
=
cdata
->
dtend
}
},
{
":sched_tag"
,
SQLITE_TEXT
,
{
.
s
=
cdata
->
sched_tag
}
},
{
":comp_flags"
,
SQLITE_INTEGER
,
{
.
i
=
comp_flags
}
},
{
NULL
,
SQLITE_NULL
,
{
.
s
=
NULL
}
}
};
if
(
cdata
->
dav
.
rowid
)
{
int
r
=
sqldb_exec
(
caldavdb
->
db
,
CMD_DELETE_JMAPCACHE
,
bval
,
NULL
,
NULL
);
if
(
r
)
return
r
;
r
=
sqldb_exec
(
caldavdb
->
db
,
CMD_UPDATE
,
bval
,
NULL
,
NULL
);
if
(
r
)
return
r
;
}
else
{
int
r
=
sqldb_exec
(
caldavdb
->
db
,
CMD_INSERT
,
bval
,
NULL
,
NULL
);
if
(
r
)
return
r
;
cdata
->
dav
.
rowid
=
sqldb_lastid
(
caldavdb
->
db
);
}
return
0
;
}
#define CMD_DELETE "DELETE FROM ical_objs WHERE rowid = :rowid;"
EXPORTED
int
caldav_delete
(
struct
caldav_db
*
caldavdb
,
unsigned
rowid
)
{
struct
sqldb_bindval
bval
[]
=
{
{
":rowid"
,
SQLITE_INTEGER
,
{
.
i
=
rowid
}
},
{
NULL
,
SQLITE_NULL
,
{
.
s
=
NULL
}
}
};
int
r
;
r
=
sqldb_exec
(
caldavdb
->
db
,
CMD_DELETE
,
bval
,
NULL
,
NULL
);
return
r
;
}
#define CMD_DELMBOX "DELETE FROM ical_objs WHERE mailbox = :mailbox;"
EXPORTED
int
caldav_delmbox
(
struct
caldav_db
*
caldavdb
,
const
char
*
mailbox
)
{
struct
sqldb_bindval
bval
[]
=
{
{
":mailbox"
,
SQLITE_TEXT
,
{
.
s
=
mailbox
}
},
{
NULL
,
SQLITE_NULL
,
{
.
s
=
NULL
}
}
};
int
r
;
r
=
sqldb_exec
(
caldavdb
->
db
,
CMD_DELMBOX
,
bval
,
NULL
,
NULL
);
return
r
;
}
EXPORTED
int
caldav_get_updates
(
struct
caldav_db
*
caldavdb
,
modseq_t
oldmodseq
,
const
char
*
mboxname
,
int
kind
,
int
limit
,
int
(
*
cb
)(
void
*
rock
,
struct
caldav_data
*
cdata
),
void
*
rock
)
{
struct
sqldb_bindval
bval
[]
=
{
{
":mailbox"
,
SQLITE_TEXT
,
{
.
s
=
mboxname
}
},
{
":modseq"
,
SQLITE_INTEGER
,
{
.
i
=
oldmodseq
}
},
{
":comp_type"
,
SQLITE_INTEGER
,
{
.
i
=
kind
}
},
/* SQLite interprets a negative limit as unbounded. */
{
":limit"
,
SQLITE_INTEGER
,
{
.
i
=
limit
>
0
?
limit
:
-1
}
},
{
NULL
,
SQLITE_NULL
,
{
.
s
=
NULL
}
}
};
static
struct
caldav_data
cdata
;
struct
read_rock
rrock
=
{
caldavdb
,
&
cdata
,
RROCK_FLAG_TOMBSTONES
,
cb
,
rock
};
struct
buf
sqlbuf
=
BUF_INITIALIZER
;
int
r
;
buf_setcstr
(
&
sqlbuf
,
CMD_READFIELDS
" WHERE"
);
if
(
mboxname
)
buf_appendcstr
(
&
sqlbuf
,
" mailbox = :mailbox AND"
);
if
(
kind
>=
0
)
{
/* Use a negative value to signal that we accept ALL components types */
buf_appendcstr
(
&
sqlbuf
,
" comp_type = :comp_type AND"
);
}
if
(
!
oldmodseq
)
buf_appendcstr
(
&
sqlbuf
,
" alive = 1 AND"
);
buf_appendcstr
(
&
sqlbuf
,
" modseq > :modseq ORDER BY modseq LIMIT :limit;"
);
r
=
sqldb_exec
(
caldavdb
->
db
,
buf_cstring
(
&
sqlbuf
),
bval
,
&
read_cb
,
&
rrock
);
buf_free
(
&
sqlbuf
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"caldav error %s"
,
error_message
(
r
));
}
return
r
;
}
static
void
check_mattach_cb
(
icalcomponent
*
comp
,
void
*
rock
)
{
int
*
mattach
=
(
int
*
)
rock
;
/* Check for managed attachment */
if
(
!*
mattach
)
{
icalproperty
*
prop
;
for
(
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_ATTACH_PROPERTY
);
prop
;
prop
=
icalcomponent_get_next_property
(
comp
,
ICAL_ATTACH_PROPERTY
))
{
if
(
icalproperty_get_managedid_parameter
(
prop
))
*
mattach
=
1
;
}
}
}
EXPORTED
int
caldav_writeentry
(
struct
caldav_db
*
caldavdb
,
struct
caldav_data
*
cdata
,
icalcomponent
*
ical
)
{
icalcomponent
*
comp
=
icalcomponent_get_first_real_component
(
ical
);
icalcomponent_kind
kind
;
icalproperty
*
prop
;
unsigned
mykind
=
0
,
recurring
=
0
,
transp
=
0
,
status
=
0
,
mattach
=
0
;
struct
icalperiodtype
span
;
/* Get iCalendar UID */
cdata
->
ical_uid
=
icalcomponent_get_uid
(
comp
);
/* Get component type and optional status */
kind
=
icalcomponent_isa
(
comp
);
switch
(
kind
)
{
case
ICAL_VEVENT_COMPONENT
:
mykind
=
CAL_COMP_VEVENT
;
switch
(
icalcomponent_get_status
(
comp
))
{
case
ICAL_STATUS_CANCELLED
:
status
=
CAL_STATUS_CANCELED
;
break
;
case
ICAL_STATUS_TENTATIVE
:
status
=
CAL_STATUS_TENTATIVE
;
break
;
default
:
status
=
CAL_STATUS_BUSY
;
break
;
}
break
;
case
ICAL_VTODO_COMPONENT
:
mykind
=
CAL_COMP_VTODO
;
break
;
case
ICAL_VJOURNAL_COMPONENT
:
mykind
=
CAL_COMP_VJOURNAL
;
break
;
case
ICAL_VFREEBUSY_COMPONENT
:
mykind
=
CAL_COMP_VFREEBUSY
;
break
;
case
ICAL_VAVAILABILITY_COMPONENT
:
mykind
=
CAL_COMP_VAVAILABILITY
;
break
;
case
ICAL_VPOLL_COMPONENT
:
mykind
=
CAL_COMP_VPOLL
;
break
;
default
:
break
;
}
cdata
->
comp_type
=
mykind
;
cdata
->
comp_flags
.
status
=
status
;
cdata
->
organizer
=
NULL
;
/* Get organizer */
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_ORGANIZER_PROPERTY
);
if
(
prop
)
{
cdata
->
organizer
=
icalproperty_get_organizer
(
prop
);
if
(
cdata
->
organizer
&&
!
strncasecmp
(
cdata
->
organizer
,
"mailto:"
,
7
))
cdata
->
organizer
+=
7
;
}
/* maybe it's only on a sub event */
icalcomponent
*
nextcomp
;
while
(
!
cdata
->
organizer
&&
(
nextcomp
=
icalcomponent_get_next_component
(
ical
,
kind
)))
{
prop
=
icalcomponent_get_first_property
(
nextcomp
,
ICAL_ORGANIZER_PROPERTY
);
if
(
prop
)
{
cdata
->
organizer
=
icalproperty_get_organizer
(
prop
);
if
(
cdata
->
organizer
&&
!
strncasecmp
(
cdata
->
organizer
,
"mailto:"
,
7
))
cdata
->
organizer
+=
7
;
}
}
/* Get transparency */
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_TRANSP_PROPERTY
);
if
(
prop
)
{
icalvalue
*
transp_val
=
icalproperty_get_value
(
prop
);
switch
(
icalvalue_get_transp
(
transp_val
))
{
case
ICAL_TRANSP_TRANSPARENT
:
case
ICAL_TRANSP_TRANSPARENTNOCONFLICT
:
transp
=
1
;
break
;
default
:
transp
=
0
;
break
;
}
}
cdata
->
comp_flags
.
transp
=
transp
;
/* Get span of component set and check for managed attachments */
span
=
icalrecurrenceset_get_utc_timespan
(
ical
,
kind
,
NULL
,
&
recurring
,
&
check_mattach_cb
,
&
mattach
);
cdata
->
dtstart
=
icaltime_as_ical_string
(
span
.
start
);
cdata
->
dtend
=
icaltime_as_ical_string
(
span
.
end
);
cdata
->
comp_flags
.
recurring
=
recurring
;
cdata
->
comp_flags
.
mattach
=
mattach
;
return
caldav_write
(
caldavdb
,
cdata
);
}
EXPORTED
char
*
caldav_mboxname
(
const
char
*
userid
,
const
char
*
name
)
{
struct
buf
boxbuf
=
BUF_INITIALIZER
;
char
*
res
=
NULL
;
buf_setcstr
(
&
boxbuf
,
config_getstring
(
IMAPOPT_CALENDARPREFIX
));
if
(
name
)
{
size_t
len
=
strcspn
(
name
,
"/"
);
buf_putc
(
&
boxbuf
,
'.'
);
buf_appendmap
(
&
boxbuf
,
name
,
len
);
}
res
=
mboxname_user_mbox
(
userid
,
buf_cstring
(
&
boxbuf
));
buf_free
(
&
boxbuf
);
return
res
;
}
EXPORTED
int
caldav_get_events
(
struct
caldav_db
*
caldavdb
,
const
char
*
asuserid
,
const
char
*
mailbox
,
const
char
*
ical_uid
,
caldav_cb_t
*
cb
,
void
*
rock
)
{
struct
sqldb_bindval
bval
[]
=
{
{
":mailbox"
,
SQLITE_TEXT
,
{
.
s
=
mailbox
}
},
{
":ical_uid"
,
SQLITE_TEXT
,
{
.
s
=
ical_uid
}
},
{
":asuserid"
,
SQLITE_TEXT
,
{
.
s
=
asuserid
}
},
{
NULL
,
SQLITE_NULL
,
{
.
s
=
NULL
}
}
};
struct
caldav_data
cdata
;
struct
read_rock
rrock
=
{
caldavdb
,
&
cdata
,
0
,
cb
,
rock
};
struct
buf
sqlbuf
=
BUF_INITIALIZER
;
buf_setcstr
(
&
sqlbuf
,
CMD_READFIELDS_JMAP
);
buf_appendcstr
(
&
sqlbuf
,
" WHERE alive = 1"
);
if
(
mailbox
)
buf_appendcstr
(
&
sqlbuf
,
" AND mailbox = :mailbox"
);
if
(
ical_uid
)
buf_appendcstr
(
&
sqlbuf
,
" AND ical_uid = :ical_uid"
);
buf_appendcstr
(
&
sqlbuf
,
" ORDER BY mailbox, imap_uid;"
);
/* XXX - tombstones */
int
r
=
sqldb_exec
(
caldavdb
->
db
,
buf_cstring
(
&
sqlbuf
),
bval
,
&
read_cb
,
&
rrock
);
buf_free
(
&
sqlbuf
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"caldav error %s"
,
error_message
(
r
));
/* XXX - free memory */
}
return
r
;
}
#define CMD_DELETE_JMAPCACHE_USER "DELETE FROM ical_jmapcache WHERE rowid = :rowid AND userid = :userid"
#define CMD_INSERT_JMAPCACHE_USER \
"INSERT INTO ical_jmapcache ( rowid, userid, jmapversion, jmapdata )" \
" VALUES ( :rowid, :userid, :jmapversion, :jmapdata );"
EXPORTED
int
caldav_write_jmapcache
(
struct
caldav_db
*
caldavdb
,
int
rowid
,
const
char
*
userid
,
int
version
,
const
char
*
data
)
{
struct
sqldb_bindval
bval
[]
=
{
{
":rowid"
,
SQLITE_INTEGER
,
{
.
i
=
rowid
}
},
{
":userid"
,
SQLITE_TEXT
,
{
.
s
=
userid
}
},
{
":jmapversion"
,
SQLITE_INTEGER
,
{
.
i
=
version
}
},
{
":jmapdata"
,
SQLITE_TEXT
,
{
.
s
=
data
}
},
{
NULL
,
SQLITE_NULL
,
{
.
s
=
NULL
}
}
};
int
r
;
/* clean up existing records if any */
r
=
sqldb_exec
(
caldavdb
->
db
,
CMD_DELETE_JMAPCACHE_USER
,
bval
,
NULL
,
NULL
);
if
(
r
)
return
r
;
/* insert the cache record */
return
sqldb_exec
(
caldavdb
->
db
,
CMD_INSERT_JMAPCACHE_USER
,
bval
,
NULL
,
NULL
);
}
struct
shareacls_rock
{
const
char
*
userid
;
char
*
principalname
;
char
*
principalacl
;
char
*
newprincipalacl
;
char
*
outboxname
;
char
*
outboxacl
;
char
*
newoutboxacl
;
hash_table
user_access
;
};
#define CALSHARE_WANTSCHED 1
#define CALSHARE_HAVESCHED 2
#define CALSHARE_WANTPRIN 4
#define CALSHARE_HAVEPRIN 8
static
int
_add_shareacls
(
const
mbentry_t
*
mbentry
,
void
*
rock
)
{
struct
shareacls_rock
*
share
=
rock
;
char
*
acl
=
xstrdup
(
mbentry
->
acl
);
int
isprincipal
=
!
strcmp
(
mbentry
->
name
,
share
->
principalname
);
int
isoutbox
=
!
strcmp
(
mbentry
->
name
,
share
->
outboxname
);
if
(
isprincipal
)
{
share
->
principalacl
=
xstrdup
(
acl
);
share
->
newprincipalacl
=
xstrdup
(
acl
);
}
if
(
isoutbox
)
{
share
->
outboxacl
=
xstrdup
(
acl
);
share
->
newoutboxacl
=
xstrdup
(
acl
);
}
char
*
userid
;
char
*
nextid
=
NULL
;
for
(
userid
=
acl
;
userid
;
userid
=
nextid
)
{
char
*
rightstr
;
int
access
;
rightstr
=
strchr
(
userid
,
'\t'
);
if
(
!
rightstr
)
break
;
*
rightstr
++
=
'\0'
;
nextid
=
strchr
(
rightstr
,
'\t'
);
if
(
!
nextid
)
break
;
*
nextid
++
=
'\0'
;
/* skip system users and owner */
if
(
is_system_user
(
userid
))
continue
;
if
(
!
strcmp
(
userid
,
share
->
userid
))
continue
;
cyrus_acl_strtomask
(
rightstr
,
&
access
);
uintptr_t
have
=
(
uintptr_t
)
hash_lookup
(
userid
,
&
share
->
user_access
);
uintptr_t
set
=
have
;
// if it's the principal, we have each user with principal read access
if
(
isprincipal
)
{
if
((
access
&
DACL_READ
)
==
DACL_READ
)
set
|=
CALSHARE_HAVEPRIN
;
}
// if it's the Outbox, we have each user with reply ability
else
if
(
isoutbox
)
{
if
((
access
&
(
DACL_INVITE
|
DACL_REPLY
))
==
(
DACL_INVITE
|
DACL_REPLY
))
set
|=
CALSHARE_HAVESCHED
;
}
// and if they can see anything else, then we NEED the above!
else
{
if
(
access
&
ACL_READ
)
set
|=
CALSHARE_WANTPRIN
;
if
(
access
&
ACL_INSERT
)
set
|=
CALSHARE_WANTSCHED
;
}
if
(
set
!=
have
)
hash_insert
(
userid
,
(
void
*
)
set
,
&
share
->
user_access
);
}
free
(
acl
);
return
0
;
}
static
void
_update_acls
(
const
char
*
userid
,
void
*
data
,
void
*
rock
)
{
struct
shareacls_rock
*
share
=
rock
;
uintptr_t
aclstatus
=
(
uintptr_t
)
data
;
if
((
aclstatus
&
CALSHARE_WANTSCHED
)
&&
!
(
aclstatus
&
CALSHARE_HAVESCHED
))
{
cyrus_acl_set
(
&
share
->
newoutboxacl
,
userid
,
ACL_MODE_ADD
,
(
DACL_INVITE
|
DACL_REPLY
),
NULL
,
NULL
);
}
if
(
!
(
aclstatus
&
CALSHARE_WANTSCHED
)
&&
(
aclstatus
&
CALSHARE_HAVESCHED
))
{
cyrus_acl_set
(
&
share
->
newoutboxacl
,
userid
,
ACL_MODE_REMOVE
,
(
DACL_INVITE
|
DACL_REPLY
),
NULL
,
NULL
);
}
if
((
aclstatus
&
CALSHARE_WANTPRIN
)
&&
!
(
aclstatus
&
CALSHARE_HAVEPRIN
))
{
cyrus_acl_set
(
&
share
->
newprincipalacl
,
userid
,
ACL_MODE_ADD
,
DACL_READ
,
NULL
,
NULL
);
}
if
(
!
(
aclstatus
&
CALSHARE_WANTPRIN
)
&&
(
aclstatus
&
CALSHARE_HAVEPRIN
))
{
cyrus_acl_set
(
&
share
->
newprincipalacl
,
userid
,
ACL_MODE_REMOVE
,
DACL_READ
,
NULL
,
NULL
);
}
}
/* update the share acls. We do this by:
* 1) iterating all the calendars for this user, looking at all the ACLs and
* tracking for each user mentioned, whether they have or need principal
* access or scheduling access.
* 2) when we see the inbox and outbox, clone the ACLs.
* 3) iterate all seen users, and decide whether we need to change the ACLs
* for either of those mailboxes.
*/
EXPORTED
int
caldav_update_shareacls
(
const
char
*
userid
)
{
struct
shareacls_rock
rock
=
{
userid
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
,
HASH_TABLE_INITIALIZER
};
construct_hash_table
(
&
rock
.
user_access
,
10
,
0
);
rock
.
principalname
=
caldav_mboxname
(
userid
,
NULL
);
rock
.
outboxname
=
caldav_mboxname
(
userid
,
SCHED_OUTBOX
);
// find out what the values should be
int
r
=
mboxlist_mboxtree
(
rock
.
principalname
,
_add_shareacls
,
&
rock
,
0
);
// did we find the ACLs? If not, bail now!
if
(
!
rock
.
principalacl
||
!
rock
.
outboxacl
)
{
r
=
IMAP_MAILBOX_NONEXISTENT
;
goto
done
;
}
// change the ACLs as required
hash_enumerate
(
&
rock
.
user_access
,
_update_acls
,
&
rock
);
if
(
strcmp
(
rock
.
principalacl
,
rock
.
newprincipalacl
))
{
r
=
mboxlist_updateacl_raw
(
rock
.
principalname
,
rock
.
newprincipalacl
);
if
(
r
)
goto
done
;
}
if
(
strcmp
(
rock
.
outboxacl
,
rock
.
newoutboxacl
))
{
r
=
mboxlist_updateacl_raw
(
rock
.
outboxname
,
rock
.
newoutboxacl
);
if
(
r
)
goto
done
;
}
done
:
free
(
rock
.
principalname
);
free
(
rock
.
principalacl
);
free
(
rock
.
newprincipalacl
);
free
(
rock
.
outboxname
);
free
(
rock
.
outboxacl
);
free
(
rock
.
newoutboxacl
);
free_hash_table
(
&
rock
.
user_access
,
NULL
);
return
r
;
}
EXPORTED
const
char
*
caldav_comp_type_as_string
(
unsigned
comp_type
)
{
switch
(
comp_type
)
{
/* "Real" components */
case
CAL_COMP_VEVENT
:
return
"VEVENT"
;
case
CAL_COMP_VTODO
:
return
"VTODO"
;
case
CAL_COMP_VJOURNAL
:
return
"VJOURNAL"
;
case
CAL_COMP_VFREEBUSY
:
return
"VFREEBUSY"
;
case
CAL_COMP_VAVAILABILITY
:
return
"VAVAILABILITY"
;
case
CAL_COMP_VPOLL
:
return
"VPOLL"
;
/* Other components */
case
CAL_COMP_VALARM
:
return
"VALARM"
;
case
CAL_COMP_VTIMEZONE
:
return
"VTIMEZONE"
;
case
CAL_COMP_VCALENDAR
:
return
"VCALENDAR"
;
default
:
return
NULL
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Sat, Apr 4, 3:27 AM (23 h, 53 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18822411
Default Alt Text
caldav_db.c (35 KB)
Attached To
Mode
R111 cyrus-imapd
Attached
Detach File
Event Timeline