Codebase list cyrus-imapd / 1be870d
New upstream version 2.4.17+caldav~beta6 Ondřej Surý 10 years ago
70 changed file(s) with 2070 addition(s) and 1384 deletion(s). Raw diff Collapse all Expand all
6363 COMPILE_ET = @COMPILE_ET@
6464
6565 PACKAGE = cyrus-imapd
66 VERSION = 2.4.17-caldav-beta5
66 VERSION = 2.4.17-caldav-beta6
6767 GIT_VERSION = $(VERSION).git$(shell date +'%Y%m%d%H%M')
6868
6969 all:: xversion
66 <title>Changes to the Cyrus IMAP Server</title>
77 </head>
88 <body>
9
10 <h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta5</h1>
11 <ul>
12 <li>RSS module now produces Atom 1.0 output rather than RSS 2.0 (we
13 prefer IETF standards)</li>
14 <li>PROPFIND/REPORT allprop/propname requests are now supported</li>
15 <li><tt>unixhierarchysep</tt> is now supported by all HTTP
16 modules</li>
17 <li>Completely rewrote write_body() - Range requests are now supported
18 on non-chunked Content-Encoded data</li>
19 <li>Added cache control response headers where appropriate to make
20 Redbot happy</li>
21 <li>Fixed handling of telemetry log file descriptors and
22 truncation</li>
23 <li>Allow GET on calendar collections for "exporting" entire
24 calendar</li>
25 <li>Fixed POST on calendar collection (add-member)</li>
26 <li>Fixed parsing of calendar-query filter</li>
27 <li>Added several CalDAV/iCalendar validation checks based on
28 CalDAVTester results</li>
29 </ul>
930
1031 <h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta4</h1>
1132 <ul>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:38 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:04 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:38 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:38 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:38 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:38 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:38 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:38 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:39 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:39 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:39 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:39 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:39 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:39 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:39 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:39 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:39 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:39 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:05 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:39 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:06 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
18591859 authentication of RSS feeds. If not set (the default), the
18601860 value of the &quot;servername&quot; option will be used.</p>
18611861
1862 <p style="margin-left:11%;"><b>rss_ttl:</b> 30</p>
1863
1864 <p style="margin-left:18%;">RSS channel time to live.
1865 Indicates to the client the number of minutes that a channel
1866 can be cached before refreshing from the source. If set to
1867 0, no indication will be made to the client. The default is
1868 30 minutes.</p>
1869
1870 <p style="margin-left:11%;"><b>rss_webmaster:</b>
1871 &lt;none&gt;</p>
1872
1873 <p style="margin-left:18%;">Email address of the person
1874 responsible for technical issues with the RSS feeds. If not
1875 set (the default), no webMaster will be included in the
1876 feeds.</p>
1877
18781862 <p style="margin-left:11%;"><b>sasl_auto_transition:</b>
18791863 0</p>
18801864
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:39 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:06 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:06 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:06 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:06 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:06 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:06 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:06 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:06 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:06 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:06 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:06 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:40 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:41 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:41 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:41 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:41 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:41 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:41 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:41 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:41 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:41 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:41 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:07 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:41 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:08 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:41 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:08 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Fri May 31 12:26:41 2013 -->
1 <!-- CreationDate: Mon Jul 1 13:58:08 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
264264 <TR><TD><A HREF="http://msdn.microsoft.com/en-us/library/aa142743%28v=exchg.65%29.aspx">Brief Header</A></TD><TD>Microsoft 'Brief' header extension</TD></TR>
265265
266266 <TR><TD COLSPAN=2><br><h2>Other</h2></TD></TR>
267 <TR><TD><A HREF="http://www.ietf.org/rfc/rfc3656.txt">RFC 3656</a></TD>
268 <TD>MUPDATE Protocol (For Cyrus Murder)</TD></TR>
267269 <TR><TD><A HREF="http://www.ietf.org/rfc/rfc5322.txt">RFC 5322</A></TD>
268270 <TD>Internet Message Format</TD></TR>
269271 <TR><TD><A HREF="http://www.ietf.org/rfc/rfc5536.txt">RFC 5536</A></TD>
270272 <TD>Netnews Article Format</TD></TR>
271273 <TR><TD><A HREF="http://www.ietf.org/rfc/rfc5537.txt">RFC 5537</A></TD>
272274 <TD>Netnews Architecture and Protocols</TD></TR>
273 <TR><TD><A HREF="http://www.ietf.org/rfc/rfc3656.txt">RFC 3656</a></TD>
274 <TD>MUPDATE Protocol (For Cyrus Murder)</TD></TR>
275 <TR><TD><A HREF="http://tools.ietf.org/html/rfc4287">RFC 4287</a></TD>
276 <TD>The Atom Syndication Format</TD></TR>
275277 <TR><TD><A HREF="http://tools.ietf.org/html/rfc5545">RFC 5545</a></TD>
276278 <TD>Internet Calendaring and Scheduling Core Object Specification (iCalendar)
277279 </TD></TR>
0 Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta5
1
2 * RSS module now produces Atom 1.0 output rather than RSS 2.0 (we
3 prefer IETF standards)
4 * PROPFIND/REPORT allprop/propname requests are now supported
5 * unixhierarchysep is now supported by all HTTP modules
6 * Completely rewrote write_body() - Range requests are now supported
7 on non-chunked Content-Encoded data
8 * Added cache control response headers where appropriate to make
9 Redbot happy
10 * Fixed handling of telemetry log file descriptors and truncation
11 * Allow GET on calendar collections for "exporting" entire calendar
12 * Fixed POST on calendar collection (add-member)
13 * Fixed parsing of calendar-query filter
14 * Added several CalDAV/iCalendar validation checks based on
15 CalDAVTester results
16
017 Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta4
118
219 * Always verify authorization credentials supplied by the client (a
259259
260260 Other
261261
262 RFC 3656 MUPDATE Protocol (For Cyrus Murder)
263
262264 RFC 5322 Internet Message Format
263265
264266 RFC 5536 Netnews Article Format
265267
266268 RFC 5537 Netnews Architecture and Protocols
267269
268 RFC 3656 MUPDATE Protocol (For Cyrus Murder)
270 RFC 4287 The Atom Syndication Format
269271
270272 RFC 5545 Internet Calendaring and Scheduling Core Object Specification
271273 (iCalendar)
118118 struct request_target_t *tgt, const char **errstr);
119119
120120 static int caldav_check_precond(struct transaction_t *txn, const void *data,
121 const char *etag, time_t lastmod,
122 unsigned long len);
121 const char *etag, time_t lastmod);
123122
124123 static int caldav_acl(struct transaction_t *txn, xmlNodePtr priv, int *rights);
125124 static int caldav_copy(struct transaction_t *txn,
129128 static int caldav_delete_sched(struct transaction_t *txn,
130129 struct mailbox *mailbox,
131130 struct index_record *record, void *data);
131 static int meth_get(struct transaction_t *txn, void *params);
132132 static int caldav_post(struct transaction_t *txn);
133133 static int caldav_put(struct transaction_t *txn, struct mailbox *mailbox,
134134 unsigned flags);
188188 { &meth_acl, &caldav_params }, /* ACL */
189189 { &meth_copy, &caldav_params }, /* COPY */
190190 { &meth_delete, &caldav_params }, /* DELETE */
191 { &meth_get_dav, &caldav_params }, /* GET */
192 { &meth_get_dav, &caldav_params }, /* HEAD */
191 { &meth_get, &caldav_params }, /* GET */
192 { &meth_get, &caldav_params }, /* HEAD */
193193 { &meth_lock, &caldav_params }, /* LOCK */
194194 { &meth_mkcol, &caldav_params }, /* MKCALENDAR */
195195 { &meth_mkcol, &caldav_params }, /* MKCOL */
237237 int r;
238238 struct mboxlist_entry mbentry;
239239 char mailboxname[MAX_MAILBOX_BUFFER], rights[100], *partition = NULL;
240 char ident[MAX_MAILBOX_NAME];
240241 struct buf acl = BUF_INITIALIZER;
241242
242243 if (config_mupdate_server && !config_getstring(IMAPOPT_PROXYSERVERS)) {
250251 }
251252
252253 /* Auto-provision calendars for 'userid' */
254
255 strlcpy(ident, userid, sizeof(ident));
256 mboxname_hiersep_toexternal(&httpd_namespace, ident, 0);
253257
254258 /* calendar-home-set */
255259 caldav_mboxname(NULL, userid, mailboxname);
261265 if (!r) {
262266 buf_reset(&acl);
263267 cyrus_acl_masktostr(ACL_ALL | DACL_READFB, rights);
264 buf_printf(&acl, "%s\t%s\t", userid, rights);
268 buf_printf(&acl, "%s\t%s\t", ident, rights);
265269 cyrus_acl_masktostr(DACL_READFB, rights);
266270 buf_printf(&acl, "%s\t%s\t", "anyone", rights);
267271 r = mboxlist_createmailbox_full(mailboxname, MBTYPE_CALENDAR,
285289 if (r == IMAP_MAILBOX_NONEXISTENT) {
286290 buf_reset(&acl);
287291 cyrus_acl_masktostr(ACL_ALL | DACL_READFB, rights);
288 buf_printf(&acl, "%s\t%s\t", userid, rights);
292 buf_printf(&acl, "%s\t%s\t", ident, rights);
289293 cyrus_acl_masktostr(DACL_READFB, rights);
290294 buf_printf(&acl, "%s\t%s\t", "anyone", rights);
291295 r = mboxlist_createmailbox_full(mailboxname, MBTYPE_CALENDAR,
302306 if (r == IMAP_MAILBOX_NONEXISTENT) {
303307 buf_reset(&acl);
304308 cyrus_acl_masktostr(ACL_ALL | DACL_SCHED, rights);
305 buf_printf(&acl, "%s\t%s\t", userid, rights);
309 buf_printf(&acl, "%s\t%s\t", ident, rights);
306310 cyrus_acl_masktostr(DACL_SCHED, rights);
307311 buf_printf(&acl, "%s\t%s\t", "anyone", rights);
308312 r = mboxlist_createmailbox_full(mailboxname, MBTYPE_CALENDAR,
319323 if (r == IMAP_MAILBOX_NONEXISTENT) {
320324 buf_reset(&acl);
321325 cyrus_acl_masktostr(ACL_ALL | DACL_SCHED, rights);
322 buf_printf(&acl, "%s\t%s\t", userid, rights);
326 buf_printf(&acl, "%s\t%s\t", ident, rights);
323327 r = mboxlist_createmailbox_full(mailboxname, MBTYPE_CALENDAR,
324328 mbentry.partition, 0,
325329 userid, httpd_authstate,
454458
455459 if (tgt->userlen) {
456460 len = snprintf(p, siz, ".%.*s", (int) tgt->userlen, tgt->user);
461 mboxname_hiersep_tointernal(&httpd_namespace, p+1, tgt->userlen);
457462 p += len;
458463 siz -= len;
459464 }
473478
474479 /* Check headers for any preconditions */
475480 static int caldav_check_precond(struct transaction_t *txn, const void *data,
476 const char *etag, time_t lastmod,
477 unsigned long len)
481 const char *etag, time_t lastmod)
478482 {
479483 const struct caldav_data *cdata = (const struct caldav_data *) data;
480484 const char *stag = cdata ? cdata->sched_tag : NULL;
482486 int precond;
483487
484488 /* Do normal WebDAV/HTTP checks (primarily for lock-token via If header) */
485 precond = check_precond(txn, data, etag, lastmod, len);
489 precond = check_precond(txn, data, etag, lastmod);
486490 if (!(precond == HTTP_OK || precond == HTTP_PARTIAL)) return precond;
487491
488492 /* Per RFC 6638, check Schedule-Tag */
707711 }
708712
709713
714 /* Perform a GET/HEAD request on a CalDAV resource */
715 static int meth_get(struct transaction_t *txn, void *params)
716 {
717 struct meth_params *gparams = (struct meth_params *) params;
718 int ret = 0, r, precond, rights;
719 struct resp_body_t *resp_body = &txn->resp_body;
720 struct buf *buf = &resp_body->payload;
721 char *server, *acl;
722 struct mailbox *mailbox = NULL;
723 static char etag[33];
724 uint32_t recno;
725 struct index_record record;
726 struct hash_table tzid_table;
727
728 /* Parse the path */
729 if ((r = gparams->parse_path(txn->req_uri->path,
730 &txn->req_tgt, &txn->error.desc))) return r;
731
732 /* GET an individual resource */
733 if (txn->req_tgt.resource) return meth_get_dav(txn, params);
734
735 /* We don't handle GET on a home-set */
736 if (!txn->req_tgt.collection) return HTTP_NO_CONTENT;
737
738 /* Locate the mailbox */
739 if ((r = http_mlookup(txn->req_tgt.mboxname, &server, &acl, NULL))) {
740 syslog(LOG_ERR, "mlookup(%s) failed: %s",
741 txn->req_tgt.mboxname, error_message(r));
742 txn->error.desc = error_message(r);
743
744 switch (r) {
745 case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN;
746 case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND;
747 default: return HTTP_SERVER_ERROR;
748 }
749 }
750
751 /* Check ACL for current user */
752 rights = acl ? cyrus_acl_myrights(httpd_authstate, acl) : 0;
753 if ((rights & DACL_READ) != DACL_READ) {
754 /* DAV:need-privileges */
755 txn->error.precond = DAV_NEED_PRIVS;
756 txn->error.resource = txn->req_tgt.path;
757 txn->error.rights = DACL_READ;
758 return HTTP_FORBIDDEN;
759 }
760
761 if (server) {
762 /* Remote mailbox */
763 struct backend *be;
764
765 be = proxy_findserver(server, &http_protocol, proxy_userid,
766 &backend_cached, NULL, NULL, httpd_in);
767 if (!be) return HTTP_UNAVAILABLE;
768
769 return http_pipe_req_resp(be, txn);
770 }
771
772 /* Local Mailbox */
773
774 if (!*gparams->davdb.db) {
775 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
776 "Check 'configdirectory' permissions or "
777 "'proxyservers' option on backend server.", proxy_userid);
778 txn->error.desc = "DAV database is not opened";
779 return HTTP_SERVER_ERROR;
780 }
781
782 /* Open mailbox for reading */
783 if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) {
784 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
785 txn->req_tgt.mboxname, error_message(r));
786 txn->error.desc = error_message(r);
787 ret = HTTP_SERVER_ERROR;
788 goto done;
789 }
790
791 /* Check any preconditions */
792 sprintf(etag, "%u-%u-%u",
793 mailbox->i.uidvalidity, mailbox->i.last_uid, mailbox->i.exists);
794 precond = gparams->check_precond(txn, NULL, etag, 0);
795
796 switch (precond) {
797 case HTTP_OK:
798 case HTTP_NOT_MODIFIED:
799 /* Fill in ETag, Expires, and Cache-Control */
800 txn->resp_body.etag = etag;
801 txn->resp_body.maxage = 3600; /* 1 hr */
802 txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; /* don't use stale data */
803
804 if (precond != HTTP_NOT_MODIFIED) break;
805
806 default:
807 /* We failed a precondition - don't perform the request */
808 ret = precond;
809 goto done;
810 }
811
812 /* Setup for chunked response */
813 txn->flags.te |= TE_CHUNKED;
814 txn->resp_body.type = gparams->content_type;
815
816 /* Short-circuit for HEAD request */
817 if (txn->meth == METH_HEAD) {
818 response_header(HTTP_OK, txn);
819 return 0;
820 }
821
822 /* iCalendar data in response should not be transformed */
823 txn->flags.cc |= CC_NOTRANSFORM;
824
825 /* Create hash table for TZIDs */
826 construct_hash_table(&tzid_table, 10, 1);
827
828 /* Begin iCalendar */
829 buf_setcstr(buf, "BEGIN:VCALENDAR\r\n");
830 buf_printf(buf, "PRODID:-//CyrusIMAP.org/Cyrus %s//EN\r\n",
831 cyrus_version());
832 buf_appendcstr(buf, "VERSION:2.0\r\n");
833 write_body(HTTP_OK, txn, buf_cstring(buf), buf_len(buf));
834
835 for (recno = 1; recno <= mailbox->i.num_records; recno++) {
836 const char *msg_base = NULL;
837 unsigned long msg_size = 0;
838 icalcomponent *ical;
839
840 if (mailbox_read_index_record(mailbox, recno, &record)) continue;
841
842 if (record.system_flags & (FLAG_EXPUNGED | FLAG_DELETED)) continue;
843
844 /* Map and parse existing iCalendar resource */
845 mailbox_map_message(mailbox, record.uid, &msg_base, &msg_size);
846 ical = icalparser_parse_string(msg_base + record.header_size);
847 mailbox_unmap_message(mailbox, record.uid, &msg_base, &msg_size);
848
849 if (ical) {
850 icalcomponent *comp;
851 const char *cal_str = NULL;
852
853 for (comp = icalcomponent_get_first_component(ical,
854 ICAL_ANY_COMPONENT);
855 comp;
856 comp = icalcomponent_get_next_component(ical,
857 ICAL_ANY_COMPONENT)) {
858 icalcomponent_kind kind = icalcomponent_isa(comp);
859
860 /* Don't duplicate any TZIDs in our iCalendar */
861 if (kind == ICAL_VTIMEZONE_COMPONENT) {
862 icalproperty *prop =
863 icalcomponent_get_first_property(comp,
864 ICAL_TZID_PROPERTY);
865 const char *tzid = icalproperty_get_tzid(prop);
866
867 if (hash_lookup(tzid, &tzid_table)) continue;
868 else hash_insert(tzid, (void *)0xDEADBEEF, &tzid_table);
869 }
870
871 /* Include this component in our iCalendar */
872 cal_str = icalcomponent_as_ical_string(comp);
873 write_body(0, txn, cal_str, strlen(cal_str));
874 }
875
876 icalcomponent_free(ical);
877 }
878 }
879
880 free_hash_table(&tzid_table, NULL);
881
882 /* End iCalendar */
883 buf_setcstr(buf, "END:VCALENDAR\r\n");
884 write_body(0, txn, buf_cstring(buf), buf_len(buf));
885
886 /* End of output */
887 write_body(0, txn, NULL, 0);
888
889 done:
890 if (mailbox) mailbox_unlock_index(mailbox, NULL);
891
892 return ret;
893 }
894
895
710896 /* Perform a busy time request, if necessary */
711897 static int caldav_post(struct transaction_t *txn)
712898 {
713899 int ret = 0, r, rights;
714 char *acl;
900 char *acl, orgid[MAX_MAILBOX_NAME+1] = "";
715901 const char **hdr;
716902 icalcomponent *ical = NULL, *comp;
717903 icalcomponent_kind kind = 0;
718904 icalproperty_method meth = 0;
719905 icalproperty *prop = NULL;
720 const char *uid = NULL, *organizer = NULL, *orgid = NULL;
906 const char *uid = NULL, *organizer = NULL;
721907 struct sched_param sparam;
722908
723909 if (!(namespace_calendar.allow & ALLOW_CAL_SCHED) || !txn->req_tgt.flags) {
795981 organizer = icalproperty_get_organizer(prop);
796982 if (organizer) {
797983 if (!caladdress_lookup(organizer, &sparam) &&
798 !(sparam.flags & SCHEDTYPE_REMOTE))
799 orgid = sparam.userid;
800 }
801
802 if (!orgid || strncmp(orgid, txn->req_tgt.user, txn->req_tgt.userlen)) {
984 !(sparam.flags & SCHEDTYPE_REMOTE)) {
985 strlcpy(orgid, sparam.userid, sizeof(orgid));
986 mboxname_hiersep_toexternal(&httpd_namespace, orgid, 0);
987 }
988 }
989
990 if (strncmp(orgid, txn->req_tgt.user, txn->req_tgt.userlen)) {
803991 txn->error.precond = CALDAV_VALID_ORGANIZER;
804992 ret = HTTP_FORBIDDEN;
805993 goto done;
9381126 (strcmp(cdata->dav.mailbox, txn->req_tgt.mboxname) ||
9391127 strcmp(cdata->dav.resource, txn->req_tgt.resource))) {
9401128 /* CALDAV:unique-scheduling-object-resource */
1129 char ext_userid[MAX_MAILBOX_NAME+1];
1130
1131 strlcpy(ext_userid, userid, sizeof(ext_userid));
1132 mboxname_hiersep_toexternal(&httpd_namespace, ext_userid, 0);
9411133
9421134 txn->error.precond = CALDAV_UNIQUE_OBJECT;
9431135 assert(!buf_len(&txn->buf));
9441136 buf_printf(&txn->buf, "%s/user/%s/%s/%s",
9451137 namespace_calendar.prefix,
946 userid, strrchr(cdata->dav.mailbox, '.')+1,
1138 ext_userid, strrchr(cdata->dav.mailbox, '.')+1,
9471139 cdata->dav.resource);
9481140 txn->error.resource = buf_cstring(&txn->buf);
9491141 ret = HTTP_FORBIDDEN;
11751367 }
11761368
11771369
1370 static int is_valid_timerange(const struct icaltimetype start,
1371 const struct icaltimetype end)
1372 {
1373 return (icaltime_is_valid_time(start) && icaltime_is_valid_time(end) &&
1374 !icaltime_is_date(start) && !icaltime_is_date(end) &&
1375 start.zone && end.zone);
1376 }
1377
1378
11781379 static int parse_comp_filter(xmlNodePtr root, struct calquery_filter *filter,
11791380 struct error_t *error)
11801381 {
11871388 if (!xmlStrcmp(node->name, BAD_CAST "comp-filter")) {
11881389 xmlChar *name = xmlGetProp(node, BAD_CAST "name");
11891390
1190 if (filter->comp) {
1191 error->precond = CALDAV_VALID_FILTER;
1192 return HTTP_FORBIDDEN;
1391 if (!filter->comp) {
1392 if (!xmlStrcmp(name, BAD_CAST "VCALENDAR"))
1393 filter->comp = CAL_COMP_VCALENDAR;
1394 else {
1395 error->precond = CALDAV_VALID_FILTER;
1396 return HTTP_FORBIDDEN;
1397 }
11931398 }
1194
1195 if (!xmlStrcmp(name, BAD_CAST "VCALENDAR"))
1196 filter->comp = CAL_COMP_VCALENDAR;
1197 else if (!xmlStrcmp(name, BAD_CAST "VEVENT"))
1198 filter->comp = CAL_COMP_VEVENT;
1199 else if (!xmlStrcmp(name, BAD_CAST "VTODO"))
1200 filter->comp = CAL_COMP_VTODO;
1201 else if (!xmlStrcmp(name, BAD_CAST "VJOURNAL"))
1202 filter->comp = CAL_COMP_VJOURNAL;
1203 else if (!xmlStrcmp(name, BAD_CAST "VFREEBUSY"))
1204 filter->comp = CAL_COMP_VFREEBUSY;
1399 else if (filter->comp == CAL_COMP_VCALENDAR) {
1400 if (!xmlStrcmp(name, BAD_CAST "VCALENDAR") ||
1401 !xmlStrcmp(name, BAD_CAST "VALARM")) {
1402 error->precond = CALDAV_VALID_FILTER;
1403 return HTTP_FORBIDDEN;
1404 }
1405 else if (!xmlStrcmp(name, BAD_CAST "VEVENT"))
1406 filter->comp |= CAL_COMP_VEVENT;
1407 else if (!xmlStrcmp(name, BAD_CAST "VTODO"))
1408 filter->comp |= CAL_COMP_VTODO;
1409 else if (!xmlStrcmp(name, BAD_CAST "VJOURNAL"))
1410 filter->comp |= CAL_COMP_VJOURNAL;
1411 else if (!xmlStrcmp(name, BAD_CAST "VFREEBUSY"))
1412 filter->comp |= CAL_COMP_VFREEBUSY;
1413 else if (!xmlStrcmp(name, BAD_CAST "VTIMEZONE"))
1414 filter->comp |= CAL_COMP_VTIMEZONE;
1415 else {
1416 error->precond = CALDAV_SUPP_FILTER;
1417 return HTTP_FORBIDDEN;
1418 }
1419 }
1420 else if (filter->comp & (CAL_COMP_VEVENT | CAL_COMP_VTODO)) {
1421 if (!xmlStrcmp(name, BAD_CAST "VALARM"))
1422 filter->comp |= CAL_COMP_VALARM;
1423 else {
1424 error->precond = CALDAV_VALID_FILTER;
1425 return HTTP_FORBIDDEN;
1426 }
1427 }
12051428 else {
12061429 error->precond = CALDAV_SUPP_FILTER;
12071430 return HTTP_FORBIDDEN;
12131436 else if (!xmlStrcmp(node->name, BAD_CAST "time-range")) {
12141437 const char *start, *end;
12151438
1439 if (!(filter->comp & (CAL_COMP_VEVENT | CAL_COMP_VTODO))) {
1440 error->precond = CALDAV_VALID_FILTER;
1441 return HTTP_FORBIDDEN;
1442 }
1443
12161444 start = (const char *) xmlGetProp(node, BAD_CAST "start");
12171445 filter->start = start ? icaltime_from_string(start) :
12181446 icaltime_from_timet_with_zone(INT_MIN, 0, NULL);
12201448 end = (const char *) xmlGetProp(node, BAD_CAST "end");
12211449 filter->end = end ? icaltime_from_string(end) :
12221450 icaltime_from_timet_with_zone(INT_MAX, 0, NULL);
1451
1452 if (!is_valid_timerange(filter->start, filter->end)) {
1453 error->precond = CALDAV_VALID_FILTER;
1454 return HTTP_FORBIDDEN;
1455 }
12231456 }
12241457 else {
12251458 error->precond = CALDAV_SUPP_FILTER;
12501483 if (!xmlStrcmp(node->name, BAD_CAST "filter")) {
12511484 memset(&calfilter, 0, sizeof(struct calquery_filter));
12521485 ret = parse_comp_filter(node->children, &calfilter, &txn->error);
1253 if (!ret) {
1486 if (ret) return ret;
1487 else {
12541488 fctx->filter = apply_calfilter;
12551489 fctx->filter_crit = &calfilter;
12561490 }
12571491 }
12581492 else if (!xmlStrcmp(node->name, BAD_CAST "timezone")) {
1493 xmlChar *tz = NULL;
1494 icalcomponent *ical = NULL;
1495
12591496 syslog(LOG_WARNING, "REPORT calendar-query w/timezone");
1497 tz = xmlNodeGetContent(node);
1498 ical = icalparser_parse_string((const char *) tz);
1499 if (!ical ||
1500 (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT) ||
1501 !icalcomponent_get_first_component(ical,
1502 ICAL_VTIMEZONE_COMPONENT)
1503 || icalcomponent_get_first_real_component(ical)) {
1504 txn->error.precond = CALDAV_VALID_DATA;
1505 return HTTP_FORBIDDEN;
1506 }
12601507 }
12611508 }
12621509 }
13071554 xmlFree(href);
13081555
13091556 /* Parse the path */
1310 if ((r = caldav_parse_path(uri.s, &tgt, fctx->errstr))) {
1557 if ((r = caldav_parse_path(uri.s, &tgt, &fctx->err->desc))) {
13111558 ret = r;
13121559 goto done;
13131560 }
15481795
15491796 end = (const char *) xmlGetProp(node, BAD_CAST "end");
15501797 if (end) calfilter.end = icaltime_from_string(end);
1798
1799 if (!is_valid_timerange(calfilter.start, calfilter.end)) {
1800 return HTTP_BAD_REQUEST;
1801 }
15511802 }
15521803 }
15531804 }
16241875 }
16251876 }
16261877
1878 /* Find iCalendar UID for the current resource, if exists */
1879 uid = icalcomponent_get_uid(comp);
1880 caldav_lookup_resource(caldavdb,
1881 mailbox->name, resource, 0, &cdata);
1882 if (cdata->ical_uid && strcmp(cdata->ical_uid, uid)) {
1883 /* CALDAV:no-uid-conflict */
1884 txn->error.precond = CALDAV_UID_CONFLICT;
1885 return HTTP_FORBIDDEN;
1886 }
1887
16271888 /* Check for existing iCalendar UID */
1628 uid = icalcomponent_get_uid(comp);
16291889 caldav_lookup_uid(caldavdb, uid, 0, &cdata);
16301890 if (cdata->dav.mailbox && !strcmp(cdata->dav.mailbox, mailbox->name) &&
16311891 strcmp(cdata->dav.resource, resource)) {
16321892 /* CALDAV:no-uid-conflict */
1893 char *owner = mboxname_to_userid(cdata->dav.mailbox);
1894 mboxname_hiersep_toexternal(&httpd_namespace, owner, 0);
1895
16331896 txn->error.precond = CALDAV_UID_CONFLICT;
16341897 assert(!buf_len(&txn->buf));
16351898 buf_printf(&txn->buf, "%s/user/%s/%s/%s",
1636 namespace_calendar.prefix,
1637 mboxname_to_userid(cdata->dav.mailbox),
1899 namespace_calendar.prefix, owner,
16381900 strrchr(cdata->dav.mailbox, '.')+1, cdata->dav.resource);
16391901 txn->error.resource = buf_cstring(&txn->buf);
16401902 return HTTP_FORBIDDEN;
16561918 }
16571919 else {
16581920 /* XXX This needs to be done via an LDAP/DB lookup */
1659 fprintf(f, "From: %s@%s\r\n", httpd_userid, config_servername);
1921 fprintf(f, "From: %s@%s\r\n", proxy_userid, config_servername);
16601922 }
16611923
16621924 fprintf(f, "Subject: %s\r\n", icalcomponent_get_summary(comp));
17502012 const char *etag = message_guid_encode(&oldrecord.guid);
17512013 time_t lastmod = oldrecord.internaldate;
17522014 int precond = caldav_check_precond(txn, cdata,
1753 etag, lastmod, 0);
2015 etag, lastmod);
17542016
17552017 overwrite = (precond == HTTP_OK);
17562018 }
18662128 calendarprefix = config_getstring(IMAPOPT_CALENDARPREFIX);
18672129 }
18682130
2131 mboxname_hiersep_tointernal(&httpd_namespace, userid, 0);
18692132 snprintf(mailboxname, sizeof(mailboxname),
18702133 "user.%s.%s", param->userid, calendarprefix);
18712134
21632426 memset(&fctx, 0, sizeof(struct propfind_ctx));
21642427 fctx.req_tgt = &txn->req_tgt;
21652428 fctx.depth = 2;
2166 fctx.userid = httpd_userid;
2429 fctx.userid = proxy_userid;
2430 fctx.int_userid = httpd_userid;
21672431 fctx.userisadmin = httpd_userisadmin;
21682432 fctx.authstate = org_authstate;
21692433 fctx.reqd_privs = 0; /* handled by CALDAV:schedule-deliver on Inbox */
21702434 fctx.filter = apply_calfilter;
21712435 fctx.filter_crit = &calfilter;
2172 fctx.errstr = &txn->error.desc;
2436 fctx.err = &txn->error;
21732437 fctx.ret = &ret;
21742438 fctx.fetcheddata = 0;
21752439
209209 size_t len;
210210 struct mboxlist_entry mbentry;
211211 char mailboxname[MAX_MAILBOX_BUFFER], rights[100], *partition = NULL;
212 char ident[MAX_MAILBOX_NAME];
212213 struct buf acl = BUF_INITIALIZER;
213214
214215 if (config_mupdate_server && !config_getstring(IMAPOPT_PROXYSERVERS)) {
222223 }
223224
224225 /* Auto-provision an addressbook for 'userid' */
226 strlcpy(ident, userid, sizeof(ident));
227 mboxname_hiersep_toexternal(&httpd_namespace, ident, 0);
225228
226229 /* Construct mailbox name corresponding to userid's Inbox */
227230 (*carddav_namespace.mboxname_tointernal)(&carddav_namespace, "INBOX",
239242 if (!r) {
240243 buf_reset(&acl);
241244 cyrus_acl_masktostr(ACL_ALL, rights);
242 buf_printf(&acl, "%s\t%s\t", userid, rights);
245 buf_printf(&acl, "%s\t%s\t", ident, rights);
243246 r = mboxlist_createmailbox_full(mailboxname, MBTYPE_ADDRESSBOOK,
244247 partition, 0,
245248 userid, httpd_authstate,
262265 if (r == IMAP_MAILBOX_NONEXISTENT) {
263266 buf_reset(&acl);
264267 cyrus_acl_masktostr(ACL_ALL, rights);
265 buf_printf(&acl, "%s\t%s\t", userid, rights);
268 buf_printf(&acl, "%s\t%s\t", ident, rights);
266269 r = mboxlist_createmailbox_full(mailboxname, MBTYPE_ADDRESSBOOK,
267270 mbentry.partition, 0,
268271 userid, httpd_authstate,
392395
393396 if (tgt->userlen) {
394397 len = snprintf(p, siz, ".%.*s", (int) tgt->userlen, tgt->user);
398 mboxname_hiersep_tointernal(&httpd_namespace, p+1, tgt->userlen);
395399 p += len;
396400 siz -= len;
397401 }
555559 xmlFree(href);
556560
557561 /* Parse the path */
558 if ((r = carddav_parse_path(uri.s, &tgt, fctx->errstr))) {
562 if ((r = carddav_parse_path(uri.s, &tgt, &fctx->err->desc))) {
559563 ret = r;
560564 goto done;
561565 }
649653 if (cdata->dav.mailbox && !strcmp(cdata->dav.mailbox, mailbox->name) &&
650654 strcmp(cdata->dav.resource, resource)) {
651655 /* CARDDAV:no-uid-conflict */
656 char *owner = mboxname_to_userid(cdata->dav.mailbox);
657 mboxname_hiersep_toexternal(&httpd_namespace, owner, 0);
658
652659 txn->error.precond = CARDDAV_UID_CONFLICT;
653660 assert(!buf_len(&txn->buf));
654661 buf_printf(&txn->buf, "%s/user/%s/%s/%s",
655 namespace_addressbook.prefix,
656 mboxname_to_userid(cdata->dav.mailbox),
662 namespace_addressbook.prefix, owner,
657663 strrchr(cdata->dav.mailbox, '.')+1, cdata->dav.resource);
658664 txn->error.resource = buf_cstring(&txn->buf);
659665 return HTTP_FORBIDDEN;
669675 /* Create iMIP header for resource */
670676
671677 /* XXX This needs to be done via an LDAP/DB lookup */
672 fprintf(f, "From: %s <>\r\n", httpd_userid);
678 fprintf(f, "From: %s <>\r\n", proxy_userid);
673679
674680 fprintf(f, "Subject: %s\r\n", fullname);
675681
756762 /* Check any preconditions */
757763 const char *etag = message_guid_encode(&oldrecord.guid);
758764 time_t lastmod = oldrecord.internaldate;
759 int precond = check_precond(txn, cdata,
760 etag, lastmod, 0);
765 int precond = check_precond(txn, cdata, etag, lastmod);
761766
762767 overwrite = (precond == HTTP_OK);
763768 }
808813 struct resp_body_t *resp_body = &txn->resp_body;
809814
810815 resp_body->loc = txn->req_tgt.path;
811 resp_body->type = "text/calendar; charset=utf-8";
816 resp_body->type = "text/vcard; charset=utf-8";
812817 resp_body->len = buf_len(&txn->req_body);
813818
814819 /* vCard data in response should not be transformed */
8282 #include <libxml/uri.h>
8383
8484
85 /* Bitmask of property flags */
86 enum {
87 PROP_ALLPROP = (1<<0), /* Returned in <allprop> request */
88 PROP_ROOT = (1<<1), /* Returnd for root (/) */
89 PROP_PRINCIPAL = (1<<2), /* Returned for principal */
90 PROP_COLLECTION = (1<<3), /* Returned for collection */
91 PROP_RESOURCE = (1<<4), /* Returned for resource */
92 PROP_CALENDAR = (1<<5), /* Returned for calendar only */
93 PROP_ADDRESSBOOK = (1<<6) /* Returned for addressbook only */
94 };
95
96 static const struct dav_namespace_t {
97 const char *href;
98 const char *prefix;
99 } known_namespaces[] = {
100 { XML_NS_DAV, "D" },
101 { XML_NS_CALDAV, "C" },
102 { XML_NS_CARDDAV, "C" },
103 { XML_NS_ISCHED, NULL },
104 { XML_NS_CS, "CS" },
105 { XML_NS_CYRUS, "CY" },
106 };
107
108 /* PROPFIND modes */
109 enum {
110 PROPFIND_NONE = 0, /* only used with REPORT */
111 PROPFIND_ALL,
112 PROPFIND_NAME,
113 PROPFIND_PROP
114 };
115
85116 static void my_dav_init(struct buf *serverinfo);
86117
87118 static int prin_parse_path(const char *path,
88119 struct request_target_t *tgt, const char **errstr);
120
121 static int allprop_cb(const char *mailbox __attribute__((unused)),
122 const char *entry,
123 const char *userid, struct annotation_data *attrib,
124 void *rock);
89125
90126 static struct meth_params princ_params = {
91127 .parse_path = &prin_parse_path
161197
162198 /* Linked-list of properties for fetching */
163199 struct propfind_entry_list {
164 xmlNodePtr prop; /* Property */
165 int (*get)(xmlNodePtr node, /* Callback to fetch property */
166 struct propfind_ctx *fctx, xmlNodePtr resp,
200 const xmlChar *name; /* Property name */
201 xmlNsPtr ns; /* Property namespace */
202 unsigned char flags; /* Flags for how/where prop apply */
203 int (*get)(const xmlChar *name, /* Callback to fetch property */
204 xmlNsPtr ns, struct propfind_ctx *fctx, xmlNodePtr resp,
167205 struct propstat propstat[], void *rock);
168206 void *rock; /* Add'l data to pass to callback */
169207 struct propfind_entry_list *next;
178216 xmlNodePtr root; /* root node to add to XML tree */
179217 xmlNsPtr *ns; /* Array of our supported namespaces */
180218 struct txn *tid; /* Transaction ID for annot writes */
181 const char **errstr; /* Error string to pass up to caller */
219 struct error_t *err; /* Error info to pass up to caller */
182220 int *ret; /* Return code to pass up to caller */
183221 struct buf buf; /* Working buffer */
184222 };
192230 { "VTODO", CAL_COMP_VTODO },
193231 { "VJOURNAL", CAL_COMP_VJOURNAL },
194232 { "VFREEBUSY", CAL_COMP_VFREEBUSY },
195 { "VTIMEZONE", CAL_COMP_VTIMEZONE },
196 { "VALARM", CAL_COMP_VALARM },
233 // { "VTIMEZONE", CAL_COMP_VTIMEZONE },
234 // { "VALARM", CAL_COMP_VALARM },
197235 { NULL, 0 }
198236 };
199237
417455 ensure_ns(respNs, NS_CYRUS, root,
418456 (const char *) nsDef->href,
419457 (const char *) nsDef->prefix);
420 else if (!xmlStrcmp(nsDef->href, BAD_CAST XML_NS_ICAL))
421 ensure_ns(respNs, NS_ICAL, root,
422 (const char *) nsDef->href,
423 (const char *) nsDef->prefix);
424458 else
425459 xmlNewNs(root, nsDef->href, nsDef->prefix);
426460 }
454488 xml_add_ns(req, respNs, root);
455489
456490 /* Set namespace of root node */
457 switch (ns) {
458 case NS_ISCHED:
459 ensure_ns(respNs, NS_ISCHED, root, XML_NS_ISCHED, NULL);
460 break;
461
462 case NS_CALDAV:
463 ensure_ns(respNs, NS_CALDAV, root, XML_NS_CALDAV, "C");
464
465 default:
466 ensure_ns(respNs, NS_DAV, root, XML_NS_DAV, "D");
467 }
491 ensure_ns(respNs, ns, root,
492 known_namespaces[ns].href, known_namespaces[ns].prefix);
468493 xmlSetNs(root, respNs[ns]);
469494
470495 return root;
500525 }
501526 else error = xmlNewChild(root, NULL, BAD_CAST "error", NULL);
502527
503 switch (precond->ns) {
504 case NS_CALDAV:
505 ensure_ns(avail_ns, NS_CALDAV, root, XML_NS_CALDAV, "C");
506 break;
507
508 case NS_CARDDAV:
509 ensure_ns(avail_ns, NS_CARDDAV, root, XML_NS_CARDDAV, "C");
510 break;
511 }
528 ensure_ns(avail_ns, precond->ns, root, known_namespaces[precond->ns].href,
529 known_namespaces[precond->ns].prefix);
512530 node = xmlNewChild(error, avail_ns[precond->ns],
513531 BAD_CAST precond->name, NULL);
514532
591609 */
592610 static xmlNodePtr xml_add_prop(long status, xmlNsPtr davns,
593611 struct propstat *propstat,
594 xmlNodePtr prop, xmlChar *content,
612 const xmlChar *name, xmlNsPtr ns,
613 xmlChar *content,
595614 unsigned precond)
596615 {
597616 xmlNodePtr newprop = NULL;
601620 xmlNewChild(propstat->root, NULL, BAD_CAST "prop", NULL);
602621 }
603622
604 if (prop) newprop = xmlNewTextChild(propstat->root->children,
605 prop->ns, prop->name, content);
623 if (name) newprop = xmlNewTextChild(propstat->root->children,
624 ns, name, content);
606625 propstat->status = status;
607626 propstat->precond = precond;
608627
609628 return newprop;
610629 }
611630
631
632 struct allprop_rock {
633 struct propfind_ctx *fctx;
634 struct propstat *propstat;
635 };
612636
613637 /* Add a response tree to 'root' for the specified href and
614638 either error code or property list */
618642
619643 resp = xmlNewChild(fctx->root, NULL, BAD_CAST "response", NULL);
620644 if (!resp) {
621 *fctx->errstr = "Unable to add response XML element";
645 fctx->err->desc = "Unable to add response XML element";
622646 *fctx->ret = HTTP_SERVER_ERROR;
623647 return HTTP_SERVER_ERROR;
624648 }
637661
638662 /* Process each property in the linked list */
639663 for (e = fctx->elist; e; e = e->next) {
664 int r = HTTP_NOT_FOUND;
665
640666 if (e->get) {
641 e->get(e->prop, fctx, resp, propstat, e->rock);
667 r = 0;
668
669 /* Pre-screen request based on prop flags */
670 switch (fctx->req_tgt->namespace) {
671 case URL_NS_CALENDAR:
672 case URL_NS_ADDRESSBOOK:
673 if (fctx->req_tgt->resource) {
674 if (!(e->flags & PROP_RESOURCE)) r = HTTP_NOT_FOUND;
675 }
676 else if (!(e->flags & PROP_COLLECTION)) r = HTTP_NOT_FOUND;
677 break;
678 }
679
680 if (!r) {
681 if (fctx->mode == PROPFIND_NAME) {
682 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
683 &propstat[PROPSTAT_OK],
684 e->name, e->ns, NULL, 0);
685 }
686 else {
687 r = e->get(e->name, e->ns,
688 fctx, resp, propstat, e->rock);
689 }
690 }
642691 }
643 else if (!(fctx->prefer & PREFER_MIN)) {
644 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
645 &propstat[PROPSTAT_NOTFOUND], e->prop, NULL, 0);
692
693 switch (r) {
694 case 0:
695 case HTTP_OK:
696 /* Nothing to do - property handled in callback */
697 break;
698
699 case HTTP_UNAUTHORIZED:
700 xml_add_prop(HTTP_UNAUTHORIZED, fctx->ns[NS_DAV],
701 &propstat[PROPSTAT_UNAUTH],
702 e->name, e->ns, NULL, 0);
703 break;
704
705 case HTTP_FORBIDDEN:
706 xml_add_prop(HTTP_FORBIDDEN, fctx->ns[NS_DAV],
707 &propstat[PROPSTAT_FORBID],
708 e->name, e->ns, NULL, 0);
709 break;
710
711 case HTTP_NOT_FOUND:
712 if (!(fctx->prefer & PREFER_MIN)) {
713 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
714 &propstat[PROPSTAT_NOTFOUND],
715 e->name, e->ns, NULL, 0);
716 }
717 break;
718
719 default:
720 xml_add_prop(r, fctx->ns[NS_DAV], &propstat[PROPSTAT_ERROR],
721 e->name, e->ns, NULL, 0);
722 break;
723
646724 }
647725 }
648726
649 /* Remove propstat 404 element if using return-minimal */
650 stat = &propstat[PROPSTAT_NOTFOUND];
651 if (stat->root && (fctx->prefer & PREFER_MIN)) {
652 xmlFreeNode(stat->root);
653 stat->root = NULL;
727 /* Process dead properties for allprop/propname */
728 if (fctx->mailbox && !fctx->req_tgt->resource &&
729 (fctx->mode == PROPFIND_ALL || fctx->mode == PROPFIND_NAME)) {
730 struct allprop_rock arock = { fctx, propstat };
731
732 annotatemore_findall(fctx->mailbox->name, ANNOT_NS "*",
733 allprop_cb, &arock, NULL);
654734 }
655735
656736 /* Check if we have any propstat elements */
658738 if (i == NUM_PROPSTAT) {
659739 /* Add an empty propstat 200 */
660740 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
661 &propstat[PROPSTAT_OK], NULL, NULL, 0);
741 &propstat[PROPSTAT_OK], NULL, NULL, NULL, 0);
662742 }
663743
664744 /* Add status and optional error to the propstat elements
686766
687767
688768 /* Callback to fetch DAV:creationdate */
689 static int propfind_creationdate(xmlNodePtr prop,
769 static int propfind_creationdate(const xmlChar *name, xmlNsPtr ns,
690770 struct propfind_ctx *fctx,
691771 xmlNodePtr resp __attribute__((unused)),
692772 struct propstat propstat[],
693773 void *rock __attribute__((unused)))
694774 {
695775 time_t t = 0;
776 struct tm *tm;
696777
697778 if (fctx->data) {
698779 struct dav_data *ddata = (struct dav_data *) fctx->data;
707788 t = sbuf.st_ctime;
708789 }
709790
710 if (t) {
711 struct tm *tm = gmtime(&t);
712
713 buf_reset(&fctx->buf);
714 buf_printf(&fctx->buf, "%4d-%02d-%02dT%02d:%02d:%02dZ",
715 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
716 tm->tm_hour, tm->tm_min, tm->tm_sec);
717
718 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
719 prop, BAD_CAST buf_cstring(&fctx->buf), 0);
720
721 }
722 else {
723 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
724 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
725 }
791 if (!t) return HTTP_NOT_FOUND;
792
793 tm = gmtime(&t);
794
795 buf_reset(&fctx->buf);
796 buf_printf(&fctx->buf, "%4d-%02d-%02dT%02d:%02d:%02dZ",
797 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
798 tm->tm_hour, tm->tm_min, tm->tm_sec);
799
800 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
801 name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
726802
727803 return 0;
728804 }
729805
730806
807 /* Callback to fetch DAV:getcontenttype */
808 static int propfind_getcontenttype(const xmlChar *name, xmlNsPtr ns,
809 struct propfind_ctx *fctx,
810 xmlNodePtr resp __attribute__((unused)),
811 struct propstat propstat[],
812 void *rock __attribute__((unused)))
813 {
814 buf_reset(&fctx->buf);
815
816 switch (fctx->req_tgt->namespace) {
817 case URL_NS_CALENDAR:
818 buf_setcstr(&fctx->buf, "text/calendar; charset=utf-8");
819 break;
820
821 case URL_NS_ADDRESSBOOK:
822 buf_setcstr(&fctx->buf, "text/vcard; charset=utf-8");
823 break;
824
825 default:
826 return HTTP_NOT_FOUND;
827 }
828
829 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
830 name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
831
832 return 0;
833 }
834
835
731836 /* Callback to fetch DAV:getcontentlength */
732 static int propfind_getlength(xmlNodePtr prop,
837 static int propfind_getlength(const xmlChar *name, xmlNsPtr ns,
733838 struct propfind_ctx *fctx,
734839 xmlNodePtr resp __attribute__((unused)),
735840 struct propstat propstat[],
737842 {
738843 uint32_t len = 0;
739844
740 if (fctx->record) len = fctx->record->size - fctx->record->header_size;
845 if (!fctx->record) return HTTP_NOT_FOUND;
846
847 len = fctx->record->size - fctx->record->header_size;
741848
742849 buf_reset(&fctx->buf);
743850 buf_printf(&fctx->buf, "%u", len);
744851 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
745 prop, BAD_CAST buf_cstring(&fctx->buf), 0);
852 name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
746853
747854 return 0;
748855 }
749856
750857
751858 /* Callback to fetch DAV:getetag */
752 static int propfind_getetag(xmlNodePtr prop,
859 static int propfind_getetag(const xmlChar *name, xmlNsPtr ns,
753860 struct propfind_ctx *fctx,
754861 xmlNodePtr resp __attribute__((unused)),
755862 struct propstat propstat[],
756863 void *rock __attribute__((unused)))
757864 {
865 if (fctx->req_tgt->resource && !fctx->record) return HTTP_NOT_FOUND;
866 if (!fctx->mailbox) return HTTP_NOT_FOUND;
867
868 buf_reset(&fctx->buf);
869
758870 if (fctx->record) {
759871 /* add DQUOTEs */
760 buf_reset(&fctx->buf);
761872 buf_printf(&fctx->buf, "\"%s\"",
762873 message_guid_encode(&fctx->record->guid));
763
764 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
765 prop, BAD_CAST buf_cstring(&fctx->buf), 0);
766874 }
767875 else {
768 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
769 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
770 }
876 buf_printf(&fctx->buf, "\"%u-%u-%u\"", fctx->mailbox->i.uidvalidity,
877 fctx->mailbox->i.last_uid, fctx->mailbox->i.exists);
878 }
879
880 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
881 name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
771882
772883 return 0;
773884 }
774885
775886
776887 /* Callback to fetch DAV:getlastmodified */
777 static int propfind_getlastmod(xmlNodePtr prop,
888 static int propfind_getlastmod(const xmlChar *name, xmlNsPtr ns,
778889 struct propfind_ctx *fctx,
779890 xmlNodePtr resp __attribute__((unused)),
780891 struct propstat propstat[],
781892 void *rock __attribute__((unused)))
782893 {
783 if (fctx->record) {
784 buf_ensure(&fctx->buf, 30);
785 httpdate_gen(fctx->buf.s, fctx->buf.alloc,
786 fctx->record->internaldate);
787
788 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
789 prop, BAD_CAST fctx->buf.s, 0);
790 }
791 else {
792 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
793 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
794 }
894 if (!fctx->record) return HTTP_NOT_FOUND;
895
896 buf_ensure(&fctx->buf, 30);
897 httpdate_gen(fctx->buf.s, fctx->buf.alloc,
898 fctx->record->internaldate);
899
900 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
901 name, ns, BAD_CAST fctx->buf.s, 0);
795902
796903 return 0;
797904 }
798905
799906
800907 /* Callback to fetch DAV:lockdiscovery */
801 static int propfind_lockdisc(xmlNodePtr prop,
908 static int propfind_lockdisc(const xmlChar *name, xmlNsPtr ns,
802909 struct propfind_ctx *fctx,
803910 xmlNodePtr resp __attribute__((unused)),
804911 struct propstat propstat[],
805912 void *rock __attribute__((unused)))
806913 {
807914 xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
808 &propstat[PROPSTAT_OK], prop, NULL, 0);
915 &propstat[PROPSTAT_OK], name, ns, NULL, 0);
809916
810917 if (fctx->mailbox && fctx->record) {
811918 struct dav_data *ddata = (struct dav_data *) fctx->data;
818925
819926
820927 /* Callback to fetch DAV:resourcetype */
821 static int propfind_restype(xmlNodePtr prop,
928 static int propfind_restype(const xmlChar *name, xmlNsPtr ns,
822929 struct propfind_ctx *fctx,
823930 xmlNodePtr resp,
824931 struct propstat propstat[],
825932 void *rock __attribute__((unused)))
826933 {
827934 xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
828 &propstat[PROPSTAT_OK], prop, NULL, 0);
935 &propstat[PROPSTAT_OK], name, ns, NULL, 0);
829936
830937 if ((fctx->req_tgt->namespace != URL_NS_DEFAULT) && !fctx->record) {
831938 xmlNewChild(node, NULL, BAD_CAST "collection", NULL);
892999 if (!cur) {
8931000 /* All resourcetypes are valid */
8941001 xml_add_prop(HTTP_OK, pctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
895 prop, NULL, 0);
1002 prop->name, prop->ns, NULL, 0);
8961003
8971004 return 0;
8981005 }
9061013 }
9071014
9081015 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV], &propstat[PROPSTAT_FORBID],
909 prop, NULL, precond);
1016 prop->name, prop->ns, NULL, precond);
9101017
9111018 *pctx->ret = HTTP_FORBIDDEN;
9121019
9151022
9161023
9171024 /* Callback to fetch DAV:supportedlock */
918 static int propfind_suplock(xmlNodePtr prop,
1025 static int propfind_suplock(const xmlChar *name, xmlNsPtr ns,
9191026 struct propfind_ctx *fctx,
9201027 xmlNodePtr resp __attribute__((unused)),
9211028 struct propstat propstat[],
9221029 void *rock __attribute__((unused)))
9231030 {
9241031 xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
925 &propstat[PROPSTAT_OK], prop, NULL, 0);
1032 &propstat[PROPSTAT_OK], name, ns, NULL, 0);
9261033
9271034 if (fctx->mailbox && fctx->record) {
9281035 xmlNodePtr entry = xmlNewChild(node, NULL, BAD_CAST "lockentry", NULL);
9381045
9391046
9401047 /* Callback to fetch DAV:supported-report-set */
941 static int propfind_reportset(xmlNodePtr prop,
1048 static int propfind_reportset(const xmlChar *name, xmlNsPtr ns,
9421049 struct propfind_ctx *fctx,
9431050 xmlNodePtr resp,
9441051 struct propstat propstat[],
9471054 xmlNodePtr s, r, top;
9481055
9491056 top = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
950 prop, NULL, 0);
1057 name, ns, NULL, 0);
9511058
9521059 if ((fctx->req_tgt->namespace == URL_NS_CALENDAR ||
9531060 fctx->req_tgt->namespace == URL_NS_ADDRESSBOOK) &&
9541061 fctx->req_tgt->collection && !fctx->req_tgt->resource) {
9551062 s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL);
9561063 r = xmlNewChild(s, NULL, BAD_CAST "report", NULL);
957 ensure_ns(fctx->ns, NS_DAV, resp->parent, XML_NS_DAV, "D");
9581064 xmlNewChild(r, fctx->ns[NS_DAV], BAD_CAST "sync-collection", NULL);
9591065 }
9601066
9611067 if (fctx->req_tgt->namespace == URL_NS_CALENDAR) {
1068 ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C");
1069
9621070 s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL);
9631071 r = xmlNewChild(s, NULL, BAD_CAST "report", NULL);
964 ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C");
9651072 xmlNewChild(r, fctx->ns[NS_CALDAV], BAD_CAST "calendar-query", NULL);
9661073
9671074 s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL);
9681075 r = xmlNewChild(s, NULL, BAD_CAST "report", NULL);
969 ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C");
9701076 xmlNewChild(r, fctx->ns[NS_CALDAV], BAD_CAST "calendar-multiget", NULL);
9711077
9721078 s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL);
9731079 r = xmlNewChild(s, NULL, BAD_CAST "report", NULL);
974 ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C");
9751080 xmlNewChild(r, fctx->ns[NS_CALDAV], BAD_CAST "free-busy-query", NULL);
9761081 }
9771082
9781083 else if (fctx->req_tgt->namespace == URL_NS_ADDRESSBOOK) {
1084 ensure_ns(fctx->ns, NS_CARDDAV, resp->parent, XML_NS_CARDDAV, "C");
1085
9791086 s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL);
9801087 r = xmlNewChild(s, NULL, BAD_CAST "report", NULL);
981 ensure_ns(fctx->ns, NS_CARDDAV, resp->parent, XML_NS_CARDDAV, "C");
9821088 xmlNewChild(r, fctx->ns[NS_CARDDAV], BAD_CAST "addressbook-query", NULL);
9831089
9841090 s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL);
9851091 r = xmlNewChild(s, NULL, BAD_CAST "report", NULL);
986 ensure_ns(fctx->ns, NS_CARDDAV, resp->parent, XML_NS_CARDDAV, "C");
9871092 xmlNewChild(r, fctx->ns[NS_CARDDAV], BAD_CAST "addressbook-multiget", NULL);
9881093 }
9891094
9921097
9931098
9941099 /* Callback to fetch DAV:principalurl */
995 static int propfind_principalurl(xmlNodePtr prop,
1100 static int propfind_principalurl(const xmlChar *name, xmlNsPtr ns,
9961101 struct propfind_ctx *fctx,
9971102 xmlNodePtr resp __attribute__((unused)),
9981103 struct propstat propstat[],
9991104 void *rock __attribute__((unused)))
10001105 {
1001 xmlNodePtr node;
1002
1003 if (fctx->req_tgt->namespace != URL_NS_PRINCIPAL) {
1004 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
1005 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
1006 }
1007 else {
1008 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1009 prop, NULL, 0);
1010
1011 buf_reset(&fctx->buf);
1012 if (fctx->req_tgt->user) {
1013 buf_printf(&fctx->buf, "%s/user/%.*s/",
1014 namespace_principal.prefix,
1015 (int) fctx->req_tgt->userlen, fctx->req_tgt->user);
1016 }
1017
1018 xml_add_href(node, NULL, buf_cstring(&fctx->buf));
1019 }
1106 xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
1107 &propstat[PROPSTAT_OK], name, ns, NULL, 0);
1108
1109 buf_reset(&fctx->buf);
1110 if (fctx->req_tgt->user) {
1111 buf_printf(&fctx->buf, "%s/user/%.*s/",
1112 namespace_principal.prefix,
1113 (int) fctx->req_tgt->userlen, fctx->req_tgt->user);
1114 }
1115
1116 xml_add_href(node, NULL, buf_cstring(&fctx->buf));
10201117
10211118 return 0;
10221119 }
10231120
10241121
10251122 /* Callback to fetch DAV:owner */
1026 static int propfind_owner(xmlNodePtr prop,
1123 static int propfind_owner(const xmlChar *name, xmlNsPtr ns,
10271124 struct propfind_ctx *fctx,
10281125 xmlNodePtr resp __attribute__((unused)),
10291126 struct propstat propstat[],
10301127 void *rock __attribute__((unused)))
10311128 {
1032 xmlNodePtr node;
1033
1034 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1035 prop, NULL, 0);
1036
1037 if ((fctx->req_tgt->namespace == URL_NS_CALENDAR ||
1038 fctx->req_tgt->namespace == URL_NS_ADDRESSBOOK) &&
1039 fctx->req_tgt->user) {
1129 xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
1130 &propstat[PROPSTAT_OK], name, ns, NULL, 0);
1131
1132 if (fctx->req_tgt->user) {
10401133 buf_reset(&fctx->buf);
10411134 buf_printf(&fctx->buf, "%s/user/%.*s/",
10421135 namespace_principal.prefix,
10701163
10711164
10721165 /* Callback to fetch DAV:supported-privilege-set */
1073 static int propfind_supprivset(xmlNodePtr prop,
1166 static int propfind_supprivset(const xmlChar *name, xmlNsPtr ns,
10741167 struct propfind_ctx *fctx,
10751168 xmlNodePtr resp,
10761169 struct propstat propstat[],
10771170 void *rock __attribute__((unused)))
10781171 {
10791172 xmlNodePtr set, all, agg, write;
1173 unsigned tgt_flags = 0;
10801174
10811175 set = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1082 prop, NULL, 0);
1176 name, ns, NULL, 0);
10831177
10841178 all = add_suppriv(set, "all", NULL, 0, "Any operation");
10851179
10871181 add_suppriv(agg, "read-current-user-privilege-set", NULL, 1,
10881182 "Read current user privilege set");
10891183
1090 if (fctx->req_tgt->namespace == URL_NS_CALENDAR &&
1091 !(fctx->req_tgt->collection &&
1092 (!strcmp(fctx->req_tgt->collection, SCHED_INBOX) ||
1093 !strcmp(fctx->req_tgt->collection, SCHED_OUTBOX)))) {
1094 ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C");
1095 add_suppriv(agg, "read-free-busy", fctx->ns[NS_CALDAV], 0,
1096 "Read free/busy time");
1184 if (fctx->req_tgt->namespace == URL_NS_CALENDAR) {
1185 if (fctx->req_tgt->collection) {
1186 ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C");
1187
1188 if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX))
1189 tgt_flags = TGT_SCHED_INBOX;
1190 else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX))
1191 tgt_flags = TGT_SCHED_OUTBOX;
1192 else {
1193 add_suppriv(agg, "read-free-busy", fctx->ns[NS_CALDAV], 0,
1194 "Read free/busy time");
1195 }
1196 }
10971197 }
10981198
10991199 write = add_suppriv(all, "write", NULL, 0, "Write any object");
11201220 add_suppriv(agg, "write-acl", NULL, 1, "Write ACL");
11211221 add_suppriv(agg, "unlock", NULL, 1, "Unlock resource");
11221222
1123 if (fctx->req_tgt->namespace == URL_NS_CALENDAR &&
1124 fctx->req_tgt->collection) {
1125 if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX)) {
1126 ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C");
1127 agg = add_suppriv(all, "schedule-deliver", fctx->ns[NS_CALDAV], 0,
1128 "Deliver scheduling messages");
1129 add_suppriv(agg, "schedule-deliver-invite", fctx->ns[NS_CALDAV], 0,
1130 "Deliver scheduling messages from Organizers");
1131 add_suppriv(agg, "schedule-deliver-reply", fctx->ns[NS_CALDAV], 0,
1132 "Deliver scheduling messages from Attendees");
1133 add_suppriv(agg, "schedule-query-freebusy", fctx->ns[NS_CALDAV], 0,
1134 "Accept free/busy requests");
1135 }
1136 else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX)) {
1137 ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C");
1138 agg = add_suppriv(all, "schedule-send", fctx->ns[NS_CALDAV], 0,
1139 "Send scheduling messages");
1140 add_suppriv(agg, "schedule-send-invite", fctx->ns[NS_CALDAV], 0,
1141 "Send scheduling messages by Organizers");
1142 add_suppriv(agg, "schedule-send-reply", fctx->ns[NS_CALDAV], 0,
1143 "Send scheduling messages by Attendees");
1144 add_suppriv(agg, "schedule-send-freebusy", fctx->ns[NS_CALDAV], 0,
1145 "Submit free/busy requests");
1146 }
1223 if (tgt_flags == TGT_SCHED_INBOX) {
1224 agg = add_suppriv(all, "schedule-deliver", fctx->ns[NS_CALDAV], 0,
1225 "Deliver scheduling messages");
1226 add_suppriv(agg, "schedule-deliver-invite", fctx->ns[NS_CALDAV], 0,
1227 "Deliver scheduling messages from Organizers");
1228 add_suppriv(agg, "schedule-deliver-reply", fctx->ns[NS_CALDAV], 0,
1229 "Deliver scheduling messages from Attendees");
1230 add_suppriv(agg, "schedule-query-freebusy", fctx->ns[NS_CALDAV], 0,
1231 "Accept free/busy requests");
1232 }
1233 else if (tgt_flags == TGT_SCHED_OUTBOX) {
1234 agg = add_suppriv(all, "schedule-send", fctx->ns[NS_CALDAV], 0,
1235 "Send scheduling messages");
1236 add_suppriv(agg, "schedule-send-invite", fctx->ns[NS_CALDAV], 0,
1237 "Send scheduling messages by Organizers");
1238 add_suppriv(agg, "schedule-send-reply", fctx->ns[NS_CALDAV], 0,
1239 "Send scheduling messages by Attendees");
1240 add_suppriv(agg, "schedule-send-freebusy", fctx->ns[NS_CALDAV], 0,
1241 "Submit free/busy requests");
11471242 }
11481243
11491244 return 0;
12631358
12641359
12651360 /* Callback to fetch DAV:current-user-privilege-set */
1266 static int propfind_curprivset(xmlNodePtr prop,
1361 static int propfind_curprivset(const xmlChar *name, xmlNsPtr ns,
12671362 struct propfind_ctx *fctx,
12681363 xmlNodePtr resp,
12691364 struct propstat propstat[],
12711366 {
12721367 int rights;
12731368 unsigned flags = 0;
1274
1275 if (!fctx->mailbox) {
1276 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
1277 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
1278 }
1279 else if (((rights =
1280 cyrus_acl_myrights(fctx->authstate, fctx->mailbox->acl))
1281 & DACL_READ) != DACL_READ) {
1282 xml_add_prop(HTTP_UNAUTHORIZED, fctx->ns[NS_DAV],
1283 &propstat[PROPSTAT_UNAUTH], prop, NULL, 0);
1284 }
1285 else {
1286 xmlNodePtr set;
1287
1288 /* Add in implicit rights */
1289 if (fctx->userisadmin) {
1290 rights |= DACL_ADMIN;
1291 }
1292 else if (mboxname_userownsmailbox(fctx->userid, fctx->mailbox->name)) {
1293 rights |= config_implicitrights;
1294 }
1295
1296 /* Build the rest of the XML response */
1297 set = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1298 prop, NULL, 0);
1299
1300 if (fctx->req_tgt->collection) {
1301 if (fctx->req_tgt->namespace == URL_NS_CALENDAR) {
1302 flags = PRIV_IMPLICIT;
1303
1304 if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX))
1305 flags = PRIV_INBOX;
1306 else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX))
1307 flags = PRIV_OUTBOX;
1308 }
1309
1310 add_privs(rights, flags, set, resp->parent, fctx->ns);
1311 }
1369 xmlNodePtr set;
1370
1371 if (!fctx->mailbox) return HTTP_NOT_FOUND;
1372 if (((rights = cyrus_acl_myrights(fctx->authstate, fctx->mailbox->acl))
1373 & DACL_READ) != DACL_READ) {
1374 return HTTP_UNAUTHORIZED;
1375 }
1376
1377 /* Add in implicit rights */
1378 if (fctx->userisadmin) {
1379 rights |= DACL_ADMIN;
1380 }
1381 else if (mboxname_userownsmailbox(fctx->int_userid, fctx->mailbox->name)) {
1382 rights |= config_implicitrights;
1383 }
1384
1385 /* Build the rest of the XML response */
1386 set = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1387 name, ns, NULL, 0);
1388
1389 if (fctx->req_tgt->collection) {
1390 if (fctx->req_tgt->namespace == URL_NS_CALENDAR) {
1391 flags = PRIV_IMPLICIT;
1392
1393 if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX))
1394 flags = PRIV_INBOX;
1395 else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX))
1396 flags = PRIV_OUTBOX;
1397 }
1398
1399 add_privs(rights, flags, set, resp->parent, fctx->ns);
13121400 }
13131401
13141402 return 0;
13161404
13171405
13181406 /* Callback to fetch DAV:acl */
1319 static int propfind_acl(xmlNodePtr prop,
1407 static int propfind_acl(const xmlChar *name, xmlNsPtr ns,
13201408 struct propfind_ctx *fctx,
13211409 xmlNodePtr resp,
13221410 struct propstat propstat[],
13231411 void *rock __attribute__((unused)))
13241412 {
13251413 int rights;
1326
1327 if (!fctx->mailbox) {
1328 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
1329 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
1330 }
1331 else if (!((rights =
1332 cyrus_acl_myrights(fctx->authstate, fctx->mailbox->acl))
1333 & DACL_ADMIN)) {
1334 xml_add_prop(HTTP_UNAUTHORIZED, fctx->ns[NS_DAV],
1335 &propstat[PROPSTAT_UNAUTH], prop, NULL, 0);
1336 }
1337 else {
1338 xmlNodePtr acl;
1339 char *aclstr, *userid;
1340 unsigned flags = 0;
1341
1342 if (fctx->req_tgt->namespace == URL_NS_CALENDAR) {
1343 flags = PRIV_IMPLICIT;
1344
1345 if (fctx->req_tgt->collection) {
1346 if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX))
1347 flags = PRIV_INBOX;
1348 else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX))
1349 flags = PRIV_OUTBOX;
1350 }
1351 }
1352
1353 /* Start the acl XML response */
1354 acl = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1355 prop, NULL, 0);
1356
1357 /* Parse the ACL string (userid/rights pairs) */
1358 userid = aclstr = xstrdup(fctx->mailbox->acl);
1359
1360 while (userid) {
1361 char *rightstr, *nextid;
1362 xmlNodePtr ace, node;
1363 int deny = 0;
1364
1365 rightstr = strchr(userid, '\t');
1366 if (!rightstr) break;
1367 *rightstr++ = '\0';
1414 xmlNodePtr acl;
1415 char *aclstr, *userid;
1416 unsigned flags = 0;
1417
1418 if (!fctx->mailbox) return HTTP_NOT_FOUND;
1419 if (!((rights = cyrus_acl_myrights(fctx->authstate, fctx->mailbox->acl))
1420 & DACL_ADMIN)) {
1421 return HTTP_UNAUTHORIZED;
1422 }
1423
1424 if (fctx->req_tgt->namespace == URL_NS_CALENDAR) {
1425 flags = PRIV_IMPLICIT;
1426
1427 if (fctx->req_tgt->collection) {
1428 if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX))
1429 flags = PRIV_INBOX;
1430 else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX))
1431 flags = PRIV_OUTBOX;
1432 }
1433 }
1434
1435 /* Start the acl XML response */
1436 acl = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1437 name, ns, NULL, 0);
1438
1439 /* Parse the ACL string (userid/rights pairs) */
1440 userid = aclstr = xstrdup(fctx->mailbox->acl);
1441
1442 while (userid) {
1443 char *rightstr, *nextid;
1444 xmlNodePtr ace, node;
1445 int deny = 0;
1446
1447 rightstr = strchr(userid, '\t');
1448 if (!rightstr) break;
1449 *rightstr++ = '\0';
13681450
1369 nextid = strchr(rightstr, '\t');
1370 if (!nextid) break;
1371 *nextid++ = '\0';
1372
1373 /* Check for negative rights */
1374 /* XXX Does this correspond to DAV:deny? */
1375 if (*userid == '-') {
1376 deny = 1;
1377 userid++;
1378 }
1379
1380 rights = cyrus_acl_strtomask(rightstr);
1381
1382 /* Add ace XML element for this userid/right pair */
1383 ace = xmlNewChild(acl, NULL, BAD_CAST "ace", NULL);
1384
1385 /* XXX Need to check for groups.
1386 * Is there any IMAP equivalent to "unauthenticated"?
1387 * Is there any DAV equivalent to "anonymous"?
1388 */
1389
1390 node = xmlNewChild(ace, NULL, BAD_CAST "principal", NULL);
1391 if (!strcmp(userid, fctx->userid))
1392 xmlNewChild(node, NULL, BAD_CAST "self", NULL);
1393 else if ((strlen(userid) == fctx->req_tgt->userlen) &&
1394 !strncmp(userid, fctx->req_tgt->user, fctx->req_tgt->userlen))
1395 xmlNewChild(node, NULL, BAD_CAST "owner", NULL);
1396 else if (!strcmp(userid, "anyone"))
1397 xmlNewChild(node, NULL, BAD_CAST "authenticated", NULL);
1398 else {
1399 buf_reset(&fctx->buf);
1400 buf_printf(&fctx->buf, "%s/user/%s/",
1401 namespace_principal.prefix, userid);
1402 xml_add_href(node, NULL, buf_cstring(&fctx->buf));
1403 }
1404
1405 node = xmlNewChild(ace, NULL,
1406 BAD_CAST (deny ? "deny" : "grant"), NULL);
1407 add_privs(rights, flags, node, resp->parent, fctx->ns);
1408
1409 if (fctx->req_tgt->resource) {
1410 node = xmlNewChild(ace, NULL, BAD_CAST "inherited", NULL);
1411 buf_reset(&fctx->buf);
1412 buf_printf(&fctx->buf, "%.*s",
1413 (int)(fctx->req_tgt->resource - fctx->req_tgt->path),
1414 fctx->req_tgt->path);
1415 xml_add_href(node, NULL, buf_cstring(&fctx->buf));
1416 }
1417
1418 userid = nextid;
1419 }
1420
1421 if (aclstr) free(aclstr);
1422 }
1451 nextid = strchr(rightstr, '\t');
1452 if (!nextid) break;
1453 *nextid++ = '\0';
1454
1455 /* Check for negative rights */
1456 /* XXX Does this correspond to DAV:deny? */
1457 if (*userid == '-') {
1458 deny = 1;
1459 userid++;
1460 }
1461
1462 rights = cyrus_acl_strtomask(rightstr);
1463
1464 /* Add ace XML element for this userid/right pair */
1465 ace = xmlNewChild(acl, NULL, BAD_CAST "ace", NULL);
1466
1467 /* XXX Need to check for groups.
1468 * Is there any IMAP equivalent to "unauthenticated"?
1469 * Is there any DAV equivalent to "anonymous"?
1470 */
1471
1472 node = xmlNewChild(ace, NULL, BAD_CAST "principal", NULL);
1473 if (!strcmp(userid, fctx->userid))
1474 xmlNewChild(node, NULL, BAD_CAST "self", NULL);
1475 else if ((strlen(userid) == fctx->req_tgt->userlen) &&
1476 !strncmp(userid, fctx->req_tgt->user, fctx->req_tgt->userlen))
1477 xmlNewChild(node, NULL, BAD_CAST "owner", NULL);
1478 else if (!strcmp(userid, "anyone"))
1479 xmlNewChild(node, NULL, BAD_CAST "authenticated", NULL);
1480 else {
1481 buf_reset(&fctx->buf);
1482 buf_printf(&fctx->buf, "%s/user/%s/",
1483 namespace_principal.prefix, userid);
1484 xml_add_href(node, NULL, buf_cstring(&fctx->buf));
1485 }
1486
1487 node = xmlNewChild(ace, NULL,
1488 BAD_CAST (deny ? "deny" : "grant"), NULL);
1489 add_privs(rights, flags, node, resp->parent, fctx->ns);
1490
1491 if (fctx->req_tgt->resource) {
1492 node = xmlNewChild(ace, NULL, BAD_CAST "inherited", NULL);
1493 buf_reset(&fctx->buf);
1494 buf_printf(&fctx->buf, "%.*s",
1495 (int)(fctx->req_tgt->resource - fctx->req_tgt->path),
1496 fctx->req_tgt->path);
1497 xml_add_href(node, NULL, buf_cstring(&fctx->buf));
1498 }
1499
1500 userid = nextid;
1501 }
1502
1503 if (aclstr) free(aclstr);
14231504
14241505 return 0;
14251506 }
14261507
14271508
14281509 /* Callback to fetch DAV:acl-restrictions */
1429 static int propfind_aclrestrict(xmlNodePtr prop,
1510 static int propfind_aclrestrict(const xmlChar *name, xmlNsPtr ns,
14301511 struct propfind_ctx *fctx,
14311512 xmlNodePtr resp __attribute__((unused)),
14321513 struct propstat propstat[],
14331514 void *rock __attribute__((unused)))
14341515 {
1435 xmlNodePtr node;
1436
1437 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1438 prop, NULL, 0);
1516 xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
1517 &propstat[PROPSTAT_OK], name, ns, NULL, 0);
14391518
14401519 xmlNewChild(node, NULL, BAD_CAST "no-invert", NULL);
14411520
14441523
14451524
14461525 /* Callback to fetch DAV:principal-collection-set */
1447 static int propfind_princolset(xmlNodePtr prop,
1526 static int propfind_princolset(const xmlChar *name, xmlNsPtr ns,
14481527 struct propfind_ctx *fctx,
14491528 xmlNodePtr resp __attribute__((unused)),
14501529 struct propstat propstat[],
14511530 void *rock __attribute__((unused)))
14521531 {
1453 xmlNodePtr node;
1454
1455 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1456 prop, NULL, 0);
1532 xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
1533 &propstat[PROPSTAT_OK], name, ns, NULL, 0);
14571534
14581535 buf_reset(&fctx->buf);
14591536 buf_printf(&fctx->buf, "%s/", namespace_principal.prefix);
14641541
14651542
14661543 /* Callback to fetch DAV:quota-available-bytes and DAV:quota-used-bytes */
1467 static int propfind_quota(xmlNodePtr prop,
1544 static int propfind_quota(const xmlChar *name, xmlNsPtr ns,
14681545 struct propfind_ctx *fctx,
14691546 xmlNodePtr resp __attribute__((unused)),
14701547 struct propstat propstat[],
14841561 }
14851562 }
14861563
1487 if (qr) {
1488 if (!fctx->quota.root ||
1489 strcmp(fctx->quota.root, qr)) {
1490 /* Different quotaroot - read it */
1491
1492 syslog(LOG_DEBUG, "reading quota for '%s'", qr);
1493
1494 fctx->quota.root = strcpy(prevroot, qr);
1495
1496 quota_read(&fctx->quota, NULL, 0);
1497 }
1498
1499 buf_reset(&fctx->buf);
1500 if (!xmlStrcmp(prop->name, BAD_CAST "quota-available-bytes")) {
1501 /* Calculate limit in bytes and subtract usage */
1502 uquota_t limit = fctx->quota.limit * QUOTA_UNITS;
1503
1504 buf_printf(&fctx->buf, UQUOTA_T_FMT, limit - fctx->quota.used);
1505 }
1506 else if (fctx->record) {
1507 /* Bytes used by resource */
1508 buf_printf(&fctx->buf, "%u", fctx->record->size);
1509 }
1510 else if (fctx->mailbox) {
1511 /* Bytes used by calendar collection */
1512 buf_printf(&fctx->buf, UQUOTA_T_FMT,
1513 fctx->mailbox->i.quota_mailbox_used);
1514 }
1515 else {
1516 /* Bytes used by entire hierarchy */
1517 buf_printf(&fctx->buf, UQUOTA_T_FMT, fctx->quota.used);
1518 }
1519
1520 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1521 prop, BAD_CAST buf_cstring(&fctx->buf), 0);
1564 if (!qr) return HTTP_NOT_FOUND;
1565
1566 if (!fctx->quota.root ||
1567 strcmp(fctx->quota.root, qr)) {
1568 /* Different quotaroot - read it */
1569
1570 syslog(LOG_DEBUG, "reading quota for '%s'", qr);
1571
1572 fctx->quota.root = strcpy(prevroot, qr);
1573
1574 quota_read(&fctx->quota, NULL, 0);
1575 }
1576
1577 buf_reset(&fctx->buf);
1578 if (!xmlStrcmp(name, BAD_CAST "quota-available-bytes")) {
1579 /* Calculate limit in bytes and subtract usage */
1580 uquota_t limit = fctx->quota.limit * QUOTA_UNITS;
1581
1582 buf_printf(&fctx->buf, UQUOTA_T_FMT, limit - fctx->quota.used);
1583 }
1584 else if (fctx->record) {
1585 /* Bytes used by resource */
1586 buf_printf(&fctx->buf, "%u", fctx->record->size);
1587 }
1588 else if (fctx->mailbox) {
1589 /* Bytes used by calendar collection */
1590 buf_printf(&fctx->buf, UQUOTA_T_FMT,
1591 fctx->mailbox->i.quota_mailbox_used);
15221592 }
15231593 else {
1524 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
1525 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
1526 }
1594 /* Bytes used by entire hierarchy */
1595 buf_printf(&fctx->buf, UQUOTA_T_FMT, fctx->quota.used);
1596 }
1597
1598 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1599 name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
15271600
15281601 return 0;
15291602 }
15301603
15311604
15321605 /* Callback to fetch DAV:current-user-principal */
1533 static int propfind_curprin(xmlNodePtr prop,
1606 static int propfind_curprin(const xmlChar *name, xmlNsPtr ns,
15341607 struct propfind_ctx *fctx,
15351608 xmlNodePtr resp __attribute__((unused)),
15361609 struct propstat propstat[],
15371610 void *rock __attribute__((unused)))
15381611 {
1539 xmlNodePtr node;
1540
1541 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1542 prop, NULL, 0);
1612 xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
1613 &propstat[PROPSTAT_OK], name, ns, NULL, 0);
15431614
15441615 if (fctx->userid) {
15451616 buf_reset(&fctx->buf);
15561627
15571628
15581629 /* Callback to fetch DAV:add-member */
1559 static int propfind_addmember(xmlNodePtr prop,
1630 static int propfind_addmember(const xmlChar *name, xmlNsPtr ns,
15601631 struct propfind_ctx *fctx,
15611632 xmlNodePtr resp __attribute__((unused)),
15621633 struct propstat propstat[],
15631634 void *rock __attribute__((unused)))
15641635 {
1565 if (fctx->req_tgt->collection && /* Until Apple Contacts is fixed */
1566 fctx->req_tgt->namespace == URL_NS_CALENDAR) {
1567 xmlNodePtr node;
1568 int len;
1569
1570 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1571 prop, NULL, 0);
1572
1573 len = fctx->req_tgt->resource ?
1574 (size_t) (fctx->req_tgt->resource - fctx->req_tgt->path) :
1575 strlen(fctx->req_tgt->path);
1576 buf_reset(&fctx->buf);
1577 buf_printf(&fctx->buf, "%.*s", len, fctx->req_tgt->path);
1578
1579 xml_add_href(node, NULL, buf_cstring(&fctx->buf));
1580 }
1581 else {
1582 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
1583 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
1584 }
1636 xmlNodePtr node;
1637 int len;
1638
1639 if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
1640
1641 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1642 name, ns, NULL, 0);
1643
1644 len = fctx->req_tgt->resource ?
1645 (size_t) (fctx->req_tgt->resource - fctx->req_tgt->path) :
1646 strlen(fctx->req_tgt->path);
1647 buf_reset(&fctx->buf);
1648 buf_printf(&fctx->buf, "%.*s", len, fctx->req_tgt->path);
1649
1650 xml_add_href(node, NULL, buf_cstring(&fctx->buf));
15851651
15861652 return 0;
15871653 }
15881654
15891655
15901656 /* Callback to fetch DAV:sync-token and CS:getctag */
1591 static int propfind_sync_token(xmlNodePtr prop,
1657 static int propfind_sync_token(const xmlChar *name, xmlNsPtr ns,
15921658 struct propfind_ctx *fctx,
15931659 xmlNodePtr resp __attribute__((unused)),
15941660 struct propstat propstat[],
15951661 void *rock __attribute__((unused)))
15961662 {
1597 if (fctx->mailbox && !fctx->record) {
1598 buf_reset(&fctx->buf);
1599 buf_printf(&fctx->buf, XML_NS_CYRUS "sync/%u-" MODSEQ_FMT,
1600 fctx->mailbox->i.uidvalidity,
1601 fctx->mailbox->i.highestmodseq);
1602
1603 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1604 prop, BAD_CAST buf_cstring(&fctx->buf), 0);
1605 }
1606 else {
1607 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
1608 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
1609 }
1663 if (!fctx->mailbox || fctx->record) return HTTP_NOT_FOUND;
1664
1665 buf_reset(&fctx->buf);
1666 buf_printf(&fctx->buf, XML_NS_CYRUS "sync/%u-" MODSEQ_FMT,
1667 fctx->mailbox->i.uidvalidity,
1668 fctx->mailbox->i.highestmodseq);
1669
1670 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1671 name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
16101672
16111673 return 0;
16121674 }
16131675
16141676
16151677 /* Callback to fetch CALDAV:calendar-data and CARDDAV:address-data */
1616 static int propfind_getdata(xmlNodePtr prop,
1678 static int propfind_getdata(const xmlChar *name, xmlNsPtr ns,
16171679 struct propfind_ctx *fctx,
16181680 xmlNodePtr resp __attribute__((unused)),
16191681 struct propstat propstat[],
16201682 void *rock __attribute__((unused)))
16211683 {
1622 if (fctx->record) {
1623 xmlNodePtr data;
1624
1625 if (!fctx->msg_base) {
1626 mailbox_map_message(fctx->mailbox, fctx->record->uid,
1627 &fctx->msg_base, &fctx->msg_size);
1628 }
1629
1630 data = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1631 prop, NULL, 0);
1632 xmlAddChild(data,
1633 xmlNewCDataBlock(fctx->root->doc,
1634 BAD_CAST fctx->msg_base +
1635 fctx->record->header_size,
1636 fctx->record->size -
1637 fctx->record->header_size));
1638
1639 fctx->fetcheddata = 1;
1640 }
1641 else {
1642 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
1643 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
1644 }
1684 xmlNodePtr data;
1685
1686 if (!fctx->record) return HTTP_NOT_FOUND;
1687
1688 if (!fctx->msg_base) {
1689 mailbox_map_message(fctx->mailbox, fctx->record->uid,
1690 &fctx->msg_base, &fctx->msg_size);
1691 }
1692
1693 data = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1694 name, ns, NULL, 0);
1695 xmlAddChild(data,
1696 xmlNewCDataBlock(fctx->root->doc,
1697 BAD_CAST fctx->msg_base +
1698 fctx->record->header_size,
1699 fctx->record->size -
1700 fctx->record->header_size));
1701
1702 fctx->fetcheddata = 1;
16451703
16461704 return 0;
16471705 }
16511709 * CALDAV:schedule-inbox-URL, CALDAV:schedule-outbox-URL,
16521710 * and CALDAV:schedule-default-calendar-URL
16531711 */
1654 static int propfind_calurl(xmlNodePtr prop,
1712 static int propfind_calurl(const xmlChar *name, xmlNsPtr ns,
16551713 struct propfind_ctx *fctx,
1656 xmlNodePtr resp,
1714 xmlNodePtr resp __attribute__((unused)),
16571715 struct propstat propstat[],
16581716 void *rock)
16591717 {
16601718 xmlNodePtr node;
16611719 const char *cal = (const char *) rock;
16621720
1663 ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C");
1664 if (fctx->userid &&
1665 /* sched-def-cal-URL only defined on sched-inbox-URL */
1666 ((fctx->req_tgt->namespace == URL_NS_CALENDAR &&
1667 fctx->req_tgt->collection && cal &&
1668 !strcmp(fctx->req_tgt->collection, SCHED_INBOX) &&
1669 !strcmp(cal, SCHED_DEFAULT))
1670 /* others only defined on principals */
1671 || (fctx->req_tgt->namespace == URL_NS_PRINCIPAL))) {
1672 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1673 prop, NULL, 0);
1674
1675 buf_reset(&fctx->buf);
1676 buf_printf(&fctx->buf, "%s/user/%s/%s",
1677 namespace_calendar.prefix, fctx->userid, cal ? cal : "");
1678
1679 xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
1680 }
1681 else {
1682 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
1683 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
1684 }
1721 if (!fctx->userid) return HTTP_NOT_FOUND;
1722
1723 /* sched-def-cal-URL only defined on sched-inbox-URL */
1724 if (!xmlStrcmp(name, BAD_CAST "schedule-default-calendar-URL") &&
1725 (!fctx->req_tgt->collection ||
1726 strcmp(fctx->req_tgt->collection, SCHED_INBOX)))
1727 return HTTP_NOT_FOUND;
1728
1729 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1730 name, ns, NULL, 0);
1731
1732 buf_reset(&fctx->buf);
1733 buf_printf(&fctx->buf, "%s/user/%s/%s",
1734 namespace_calendar.prefix, fctx->userid, cal ? cal : "");
1735
1736 xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
16851737
16861738 return 0;
16871739 }
16881740
16891741
16901742 /* Callback to fetch CALDAV:supported-calendar-component-set */
1691 static int propfind_calcompset(xmlNodePtr prop,
1743 static int propfind_calcompset(const xmlChar *name, xmlNsPtr ns,
16921744 struct propfind_ctx *fctx,
16931745 xmlNodePtr resp __attribute__((unused)),
16941746 struct propstat propstat[],
16951747 void *rock __attribute__((unused)))
16961748 {
1749 const char *prop_annot = ANNOT_NS "CALDAV:supported-calendar-component-set";
16971750 struct annotation_data attrib;
16981751 unsigned long types = 0;
1752 xmlNodePtr set, node;
1753 const struct cal_comp_t *comp;
16991754 int r = 0;
17001755
1701 if ((fctx->req_tgt->namespace == URL_NS_CALENDAR) &&
1702 fctx->req_tgt->collection && !fctx->req_tgt->resource) {
1703 const char *prop_annot =
1704 ANNOT_NS "CALDAV:supported-calendar-component-set";
1705
1706 if (!(r = annotatemore_lookup(fctx->mailbox->name, prop_annot,
1707 /* shared */ "", &attrib))) {
1708 if (attrib.value)
1709 types = strtoul(attrib.value, NULL, 10);
1710 else
1711 types = -1; /* ALL components types */
1712 }
1713 }
1714
1715 if (r) {
1716 xml_add_prop(HTTP_SERVER_ERROR, fctx->ns[NS_DAV],
1717 &propstat[PROPSTAT_ERROR], prop, NULL, 0);
1718 }
1719 else if (types) {
1720 xmlNodePtr set, node;
1721 const struct cal_comp_t *comp;
1722
1723 set = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1724 prop, NULL, 0);
1725 /* Create "comp" elements from the stored bitmask */
1726 for (comp = cal_comps; comp->name; comp++) {
1727 if (types & comp->type) {
1728 node = xmlNewChild(set, fctx->ns[NS_CALDAV],
1729 BAD_CAST "comp", NULL);
1730 xmlNewProp(node, BAD_CAST "name", BAD_CAST comp->name);
1731 }
1732 }
1733 }
1734 else {
1735 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
1736 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
1756 if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
1757
1758 if (!(r = annotatemore_lookup(fctx->mailbox->name, prop_annot,
1759 /* shared */ "", &attrib))) {
1760 if (attrib.value)
1761 types = strtoul(attrib.value, NULL, 10);
1762 else
1763 types = -1; /* ALL components types */
1764 }
1765
1766 if (r) return HTTP_SERVER_ERROR;
1767 if (!types) return HTTP_NOT_FOUND;
1768
1769 set = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1770 name, ns, NULL, 0);
1771 /* Create "comp" elements from the stored bitmask */
1772 for (comp = cal_comps; comp->name; comp++) {
1773 if (types & comp->type) {
1774 node = xmlNewChild(set, fctx->ns[NS_CALDAV],
1775 BAD_CAST "comp", NULL);
1776 xmlNewProp(node, BAD_CAST "name", BAD_CAST comp->name);
1777 }
17371778 }
17381779
17391780 return 0;
17841825 buf_cstring(&pctx->buf), NULL,
17851826 buf_len(&pctx->buf), 0,
17861827 &pctx->tid))) {
1787 xml_add_prop(HTTP_OK, pctx->ns[NS_DAV],
1788 &propstat[PROPSTAT_OK], prop, NULL, 0);
1828 xml_add_prop(HTTP_OK, pctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1829 prop->name, prop->ns, NULL, 0);
17891830 }
17901831 else {
17911832 xml_add_prop(HTTP_SERVER_ERROR, pctx->ns[NS_DAV],
1792 &propstat[PROPSTAT_ERROR], prop, NULL, 0);
1833 &propstat[PROPSTAT_ERROR],
1834 prop->name, prop->ns, NULL, 0);
17931835 }
17941836
17951837 return 0;
18041846 }
18051847
18061848 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV], &propstat[PROPSTAT_FORBID],
1807 prop, NULL, precond);
1849 prop->name, prop->ns, NULL, precond);
18081850
18091851 *pctx->ret = HTTP_FORBIDDEN;
18101852
18121854 }
18131855
18141856 /* Callback to fetch CALDAV:supported-calendar-data */
1815 static int propfind_suppcaldata(xmlNodePtr prop,
1857 static int propfind_suppcaldata(const xmlChar *name, xmlNsPtr ns,
18161858 struct propfind_ctx *fctx,
18171859 xmlNodePtr resp __attribute__((unused)),
18181860 struct propstat propstat[],
18201862 {
18211863 xmlNodePtr node;
18221864
1823 if ((fctx->req_tgt->namespace == URL_NS_CALENDAR) &&
1824 fctx->req_tgt->collection && !fctx->req_tgt->resource) {
1825 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1826 prop, NULL, 0);
1827
1828 node = xmlNewChild(node, fctx->ns[NS_CALDAV],
1829 BAD_CAST "calendar-data", NULL);
1830 xmlNewProp(node, BAD_CAST "content-type", BAD_CAST "text/calendar");
1831 xmlNewProp(node, BAD_CAST "version", BAD_CAST "2.0");
1832 }
1833 else {
1834 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
1835 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
1836 }
1865 if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
1866
1867 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1868 name, ns, NULL, 0);
1869
1870 node = xmlNewChild(node, fctx->ns[NS_CALDAV],
1871 BAD_CAST "calendar-data", NULL);
1872 xmlNewProp(node, BAD_CAST "content-type", BAD_CAST "text/calendar");
1873 xmlNewProp(node, BAD_CAST "version", BAD_CAST "2.0");
18371874
18381875 return 0;
18391876 }
18401877
18411878
18421879 /* Callback to fetch CALDAV:schedule-tag */
1843 static int propfind_schedtag(xmlNodePtr prop,
1880 static int propfind_schedtag(const xmlChar *name, xmlNsPtr ns,
18441881 struct propfind_ctx *fctx,
1845 xmlNodePtr resp,
1882 xmlNodePtr resp __attribute__((unused)),
18461883 struct propstat propstat[],
18471884 void *rock __attribute__((unused)))
18481885 {
18491886 struct caldav_data *cdata = (struct caldav_data *) fctx->data;
18501887
1851 ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C");
1852 if (cdata->sched_tag) {
1853 /* add DQUOTEs */
1854 buf_reset(&fctx->buf);
1855 buf_printf(&fctx->buf, "\"%s\"", cdata->sched_tag);
1856
1857 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1858 prop, BAD_CAST buf_cstring(&fctx->buf), 0);
1859 }
1860 else {
1861 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
1862 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
1863 }
1888 if (!cdata->sched_tag) return HTTP_NOT_FOUND;
1889
1890 /* add DQUOTEs */
1891 buf_reset(&fctx->buf);
1892 buf_printf(&fctx->buf, "\"%s\"", cdata->sched_tag);
1893
1894 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1895 name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
18641896
18651897 return 0;
18661898 }
18671899
18681900
18691901 /* Callback to fetch CALDAV:calendar-user-address-set */
1870 static int propfind_caluseraddr(xmlNodePtr prop,
1902 static int propfind_caluseraddr(const xmlChar *name, xmlNsPtr ns,
18711903 struct propfind_ctx *fctx,
1872 xmlNodePtr resp,
1904 xmlNodePtr resp __attribute__((unused)),
18731905 struct propstat propstat[],
18741906 void *rock __attribute__((unused)))
18751907 {
18761908 xmlNodePtr node;
18771909
1878 ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C");
1879 if (fctx->userid) {
1880 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1881 prop, NULL, 0);
1882
1883 /* XXX This needs to be done via an LDAP/DB lookup */
1884 buf_reset(&fctx->buf);
1885 buf_printf(&fctx->buf, "mailto:%s@%s", fctx->userid, config_servername);
1886
1887 xmlNewChild(node, fctx->ns[NS_DAV], BAD_CAST "href",
1888 BAD_CAST buf_cstring(&fctx->buf));
1889 }
1890 else {
1891 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
1892 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
1893 }
1910 if (!fctx->userid) return HTTP_NOT_FOUND;
1911
1912 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1913 name, ns, NULL, 0);
1914
1915 /* XXX This needs to be done via an LDAP/DB lookup */
1916 buf_reset(&fctx->buf);
1917 buf_printf(&fctx->buf, "mailto:%s@%s", fctx->userid, config_servername);
1918
1919 xmlNewChild(node, fctx->ns[NS_DAV], BAD_CAST "href",
1920 BAD_CAST buf_cstring(&fctx->buf));
18941921
18951922 return 0;
18961923 }
18971924
18981925
18991926 /* Callback to fetch CALDAV:schedule-calendar-transp */
1900 static int propfind_caltransp(xmlNodePtr prop,
1927 static int propfind_caltransp(const xmlChar *name, xmlNsPtr ns,
19011928 struct propfind_ctx *fctx,
1902 xmlNodePtr resp,
1929 xmlNodePtr resp __attribute__((unused)),
19031930 struct propstat propstat[],
19041931 void *rock __attribute__((unused)))
19051932 {
1933 const char *prop_annot = ANNOT_NS "CALDAV:schedule-calendar-transp";
19061934 struct annotation_data attrib;
19071935 const char *value = NULL;
1936 xmlNodePtr node;
19081937 int r = 0;
19091938
1910 if ((fctx->req_tgt->namespace == URL_NS_CALENDAR) &&
1911 fctx->req_tgt->collection && !fctx->req_tgt->resource) {
1912 const char *prop_annot =
1913 ANNOT_NS "CALDAV:schedule-calendar-transp";
1914
1915 if (!(r = annotatemore_lookup(fctx->mailbox->name, prop_annot,
1916 /* shared */ "", &attrib))
1917 && attrib.value) {
1918 value = attrib.value;
1919 }
1920 }
1921
1922 ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C");
1923 if (r) {
1924 xml_add_prop(HTTP_SERVER_ERROR, fctx->ns[NS_DAV],
1925 &propstat[PROPSTAT_ERROR], prop, NULL, 0);
1926 }
1927 else if (value) {
1928 xmlNodePtr node;
1929
1930 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1931 prop, NULL, 0);
1932 xmlNewChild(node, fctx->ns[NS_CALDAV], BAD_CAST value, NULL);
1933 }
1934 else {
1935 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
1936 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
1937 }
1939 if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
1940
1941 if (!(r = annotatemore_lookup(fctx->mailbox->name, prop_annot,
1942 /* shared */ "", &attrib)) && attrib.value) {
1943 value = attrib.value;
1944 }
1945
1946 if (r) return HTTP_SERVER_ERROR;
1947 if (!value) return HTTP_NOT_FOUND;
1948
1949 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1950 name, ns, NULL, 0);
1951 xmlNewChild(node, fctx->ns[NS_CALDAV], BAD_CAST value, NULL);
19381952
19391953 return 0;
19401954 }
19681982 else {
19691983 /* Unknown value */
19701984 xml_add_prop(HTTP_CONFLICT, pctx->ns[NS_DAV],
1971 &propstat[PROPSTAT_CONFLICT], prop, NULL, 0);
1985 &propstat[PROPSTAT_CONFLICT],
1986 prop->name, prop->ns, NULL, 0);
19721987
19731988 *pctx->ret = HTTP_FORBIDDEN;
19741989
19831998 strlen(transp), 0,
19841999 &pctx->tid)) {
19852000 xml_add_prop(HTTP_OK, pctx->ns[NS_DAV],
1986 &propstat[PROPSTAT_OK], prop, NULL, 0);
2001 &propstat[PROPSTAT_OK], prop->name, prop->ns, NULL, 0);
19872002 }
19882003 else {
19892004 xml_add_prop(HTTP_SERVER_ERROR, pctx->ns[NS_DAV],
1990 &propstat[PROPSTAT_ERROR], prop, NULL, 0);
2005 &propstat[PROPSTAT_ERROR],
2006 prop->name, prop->ns, NULL, 0);
19912007 }
19922008 }
19932009 else {
19942010 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
1995 &propstat[PROPSTAT_FORBID], prop, NULL, 0);
2011 &propstat[PROPSTAT_FORBID],
2012 prop->name, prop->ns, NULL, 0);
19962013
19972014 *pctx->ret = HTTP_FORBIDDEN;
19982015 }
20012018 }
20022019
20032020
2021 /* Callback to write calendar-timezone property */
2022 static int proppatch_timezone(xmlNodePtr prop, unsigned set,
2023 struct proppatch_ctx *pctx,
2024 struct propstat propstat[],
2025 void *rock __attribute__((unused)))
2026 {
2027 if ((pctx->req_tgt->namespace == URL_NS_CALENDAR) &&
2028 pctx->req_tgt->collection && !pctx->req_tgt->resource) {
2029 xmlChar *freeme = NULL;
2030 const char *value = NULL;
2031 size_t len = 0;
2032 unsigned valid = 1;
2033
2034 if (set) {
2035 icalcomponent *ical = NULL;
2036
2037 freeme = xmlNodeGetContent(prop);
2038 value = (const char *) freeme;
2039 len = strlen(value);
2040
2041 /* Parse and validate the iCal data */
2042 ical = icalparser_parse_string(value);
2043 if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
2044 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
2045 &propstat[PROPSTAT_FORBID],
2046 prop->name, prop->ns, NULL,
2047 CALDAV_VALID_DATA);
2048 *pctx->ret = HTTP_FORBIDDEN;
2049 valid = 0;
2050 }
2051 else if (!icalcomponent_get_first_component(ical,
2052 ICAL_VTIMEZONE_COMPONENT)
2053 || icalcomponent_get_first_real_component(ical)) {
2054 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
2055 &propstat[PROPSTAT_FORBID],
2056 prop->name, prop->ns, NULL,
2057 CALDAV_VALID_OBJECT);
2058 *pctx->ret = HTTP_FORBIDDEN;
2059 valid = 0;
2060 }
2061 }
2062
2063 if (valid) {
2064 buf_reset(&pctx->buf);
2065 buf_printf(&pctx->buf, ANNOT_NS "<%s>%s",
2066 (const char *) prop->ns->href, prop->name);
2067
2068 if (!annotatemore_write_entry(pctx->mailboxname,
2069 buf_cstring(&pctx->buf),
2070 /* shared */ "", value, NULL,
2071 len, 0, &pctx->tid)) {
2072 xml_add_prop(HTTP_OK, pctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
2073 prop->name, prop->ns, NULL, 0);
2074 }
2075 else {
2076 xml_add_prop(HTTP_SERVER_ERROR, pctx->ns[NS_DAV],
2077 &propstat[PROPSTAT_ERROR],
2078 prop->name, prop->ns, NULL, 0);
2079 }
2080 }
2081
2082 if (freeme) xmlFree(freeme);
2083 }
2084 else {
2085 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
2086 &propstat[PROPSTAT_FORBID], prop->name, prop->ns, NULL, 0);
2087
2088 *pctx->ret = HTTP_FORBIDDEN;
2089 }
2090
2091 return 0;
2092 }
2093
2094
20042095 /* Callback to fetch CARDDAV:addressbook-home-set */
2005 static int propfind_abookurl(xmlNodePtr prop,
2096 static int propfind_abookurl(const xmlChar *name, xmlNsPtr ns,
20062097 struct propfind_ctx *fctx,
2007 xmlNodePtr resp,
2098 xmlNodePtr resp __attribute__((unused)),
20082099 struct propstat propstat[],
20092100 void *rock)
20102101 {
20112102 xmlNodePtr node;
20122103 const char *abook = (const char *) rock;
20132104
2014 ensure_ns(fctx->ns, NS_CARDDAV, resp->parent, XML_NS_CARDDAV, "C");
2015 if (fctx->userid &&
2016 (fctx->req_tgt->namespace == URL_NS_PRINCIPAL)) {
2017 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
2018 prop, NULL, 0);
2019
2020 buf_reset(&fctx->buf);
2021 buf_printf(&fctx->buf, "%s/user/%s/%s", namespace_addressbook.prefix,
2022 fctx->userid, abook ? abook : "");
2023
2024 xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
2025 }
2026 else {
2027 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
2028 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
2029 }
2105 if (!fctx->userid) return HTTP_NOT_FOUND;
2106
2107 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
2108 name, ns, NULL, 0);
2109
2110 buf_reset(&fctx->buf);
2111 buf_printf(&fctx->buf, "%s/user/%s/%s", namespace_addressbook.prefix,
2112 fctx->userid, abook ? abook : "");
2113
2114 xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
20302115
20312116 return 0;
20322117 }
20332118
20342119
20352120 /* Callback to fetch CARDDAV:supported-address-data */
2036 static int propfind_suppaddrdata(xmlNodePtr prop,
2121 static int propfind_suppaddrdata(const xmlChar *name, xmlNsPtr ns,
20372122 struct propfind_ctx *fctx,
20382123 xmlNodePtr resp __attribute__((unused)),
20392124 struct propstat propstat[],
20412126 {
20422127 xmlNodePtr node;
20432128
2044 if ((fctx->req_tgt->namespace == URL_NS_ADDRESSBOOK) &&
2045 fctx->req_tgt->collection && !fctx->req_tgt->resource) {
2046 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
2047 prop, NULL, 0);
2048
2049 node = xmlNewChild(node, fctx->ns[NS_CARDDAV],
2050 BAD_CAST "address-data-type", NULL);
2051 xmlNewProp(node, BAD_CAST "content-type", BAD_CAST "text/vcard");
2052 xmlNewProp(node, BAD_CAST "version", BAD_CAST "3.0");
2053 }
2054 else {
2055 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
2056 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
2057 }
2129 if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
2130
2131 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
2132 name, ns, NULL, 0);
2133
2134 node = xmlNewChild(node, fctx->ns[NS_CARDDAV],
2135 BAD_CAST "address-data-type", NULL);
2136 xmlNewProp(node, BAD_CAST "content-type", BAD_CAST "text/vcard");
2137 xmlNewProp(node, BAD_CAST "version", BAD_CAST "3.0");
20582138
20592139 return 0;
20602140 }
20612141
20622142
20632143 /* Callback to fetch properties from resource header */
2064 static int propfind_fromhdr(xmlNodePtr prop,
2144 static int propfind_fromhdr(const xmlChar *name, xmlNsPtr ns,
20652145 struct propfind_ctx *fctx,
20662146 xmlNodePtr resp __attribute__((unused)),
20672147 struct propstat propstat[],
20682148 void *hdrname)
20692149 {
2070 if (fctx->record) {
2071 if (mailbox_cached_header((const char *) hdrname) != BIT32_MAX &&
2072 !mailbox_cacherecord(fctx->mailbox, fctx->record)) {
2073 unsigned size;
2074 struct protstream *stream;
2075 hdrcache_t hdrs = NULL;
2076 const char **hdr;
2077
2078 size = cacheitem_size(fctx->record, CACHE_HEADERS);
2079 stream = prot_readmap(cacheitem_base(fctx->record,
2080 CACHE_HEADERS), size);
2081 hdrs = spool_new_hdrcache();
2082 spool_fill_hdrcache(stream, NULL, hdrs, NULL);
2083 prot_free(stream);
2084
2085 if ((hdr = spool_getheader(hdrs, (const char *) hdrname))) {
2086 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
2087 prop, BAD_CAST hdr[0], 0);
2088 }
2089
2090 spool_free_hdrcache(hdrs);
2091
2092 if (hdr) return 0;
2093 }
2094 }
2095
2096 xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV], &propstat[PROPSTAT_NOTFOUND],
2097 prop, NULL, 0);
2098
2099 return 0;
2150 int r = HTTP_NOT_FOUND;
2151
2152 if (!fctx->record) return HTTP_NOT_FOUND;
2153
2154 if (mailbox_cached_header((const char *) hdrname) != BIT32_MAX &&
2155 !mailbox_cacherecord(fctx->mailbox, fctx->record)) {
2156 unsigned size;
2157 struct protstream *stream;
2158 hdrcache_t hdrs = NULL;
2159 const char **hdr;
2160
2161 size = cacheitem_size(fctx->record, CACHE_HEADERS);
2162 stream = prot_readmap(cacheitem_base(fctx->record,
2163 CACHE_HEADERS), size);
2164 hdrs = spool_new_hdrcache();
2165 spool_fill_hdrcache(stream, NULL, hdrs, NULL);
2166 prot_free(stream);
2167
2168 if ((hdr = spool_getheader(hdrs, (const char *) hdrname))) {
2169 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
2170 name, ns, BAD_CAST hdr[0], 0);
2171 r = 0;
2172 }
2173
2174 spool_free_hdrcache(hdrs);
2175 }
2176
2177 return r;
21002178 }
21012179
21022180
21032181 /* Callback to read a property from annotation DB */
2104 static int propfind_fromdb(xmlNodePtr prop,
2182 static int propfind_fromdb(const xmlChar *name, xmlNsPtr ns,
21052183 struct propfind_ctx *fctx,
21062184 xmlNodePtr resp __attribute__((unused)),
21072185 struct propstat propstat[],
21132191
21142192 buf_reset(&fctx->buf);
21152193 buf_printf(&fctx->buf, ANNOT_NS "<%s>%s",
2116 (const char *) prop->ns->href, prop->name);
2194 (const char *) ns->href, name);
21172195
21182196 memset(&attrib, 0, sizeof(struct annotation_data));
21192197
21212199 !(r = annotatemore_lookup(fctx->mailbox->name, buf_cstring(&fctx->buf),
21222200 /* shared */ "", &attrib))) {
21232201 if (!attrib.value &&
2124 !xmlStrcmp(prop->name, BAD_CAST "displayname")) {
2202 !xmlStrcmp(name, BAD_CAST "displayname")) {
21252203 /* Special case empty displayname -- use last segment of path */
21262204 attrib.value = strrchr(fctx->mailbox->name, '.') + 1;
21272205 attrib.size = strlen(attrib.value);
21282206 }
21292207 }
21302208
2131 if (r) {
2132 node = xml_add_prop(HTTP_SERVER_ERROR, fctx->ns[NS_DAV],
2133 &propstat[PROPSTAT_ERROR], prop, NULL, 0);
2134 }
2135 else if (attrib.value) {
2136 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
2137 prop, NULL, 0);
2138 xmlAddChild(node, xmlNewCDataBlock(fctx->root->doc,
2139 BAD_CAST attrib.value, attrib.size));
2140 }
2141 else {
2142 node = xml_add_prop(HTTP_NOT_FOUND, fctx->ns[NS_DAV],
2143 &propstat[PROPSTAT_NOTFOUND], prop, NULL, 0);
2144 }
2209 if (r) return HTTP_SERVER_ERROR;
2210 if (!attrib.value) return HTTP_NOT_FOUND;
2211
2212 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
2213 name, ns, NULL, 0);
2214 xmlAddChild(node, xmlNewCDataBlock(fctx->root->doc,
2215 BAD_CAST attrib.value, attrib.size));
21452216
21462217 return 0;
21472218 }
21732244 value, NULL, len, 0,
21742245 &pctx->tid))) {
21752246 xml_add_prop(HTTP_OK, pctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
2176 prop, NULL, 0);
2247 prop->name, prop->ns, NULL, 0);
21772248 }
21782249 else {
21792250 xml_add_prop(HTTP_SERVER_ERROR, pctx->ns[NS_DAV],
2180 &propstat[PROPSTAT_ERROR], prop, NULL, 0);
2251 &propstat[PROPSTAT_ERROR], prop->name, prop->ns, NULL, 0);
21812252 }
21822253
21832254 if (freeme) xmlFree(freeme);
21892260 /* Array of known "live" properties */
21902261 static const struct prop_entry {
21912262 const char *name; /* Property name */
2192 const char *ns; /* Property namespace */
2193 unsigned allprop; /* Should we fetch for allprop? */
2194 int (*get)(xmlNodePtr node, /* Callback to fetch property */
2195 struct propfind_ctx *fctx, xmlNodePtr resp,
2263 unsigned ns; /* Property namespace */
2264 unsigned char flags; /* Flags for how/where props apply */
2265 int (*get)(const xmlChar *name, /* Callback to fetch property */
2266 xmlNsPtr ns, struct propfind_ctx *fctx, xmlNodePtr resp,
21962267 struct propstat propstat[], void *rock);
21972268 int (*put)(xmlNodePtr prop, /* Callback to write property */
21982269 unsigned set, struct proppatch_ctx *pctx,
22012272 } prop_entries[] = {
22022273
22032274 /* WebDAV (RFC 4918) properties */
2204 { "creationdate", XML_NS_DAV, 1, propfind_creationdate, NULL, NULL },
2205 { "displayname", XML_NS_DAV, 1, propfind_fromdb, proppatch_todb, NULL },
2206 { "getcontentlanguage", XML_NS_DAV, 1,
2275 { "creationdate", NS_DAV,
2276 PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
2277 propfind_creationdate, NULL, NULL },
2278 { "displayname", NS_DAV,
2279 PROP_ALLPROP | PROP_PRINCIPAL | PROP_COLLECTION | PROP_RESOURCE,
2280 propfind_fromdb, proppatch_todb, NULL },
2281 { "getcontentlanguage", NS_DAV, PROP_ALLPROP | PROP_RESOURCE,
22072282 propfind_fromhdr, NULL, "Content-Language" },
2208 { "getcontentlength", XML_NS_DAV, 1, propfind_getlength, NULL, NULL },
2209 { "getcontenttype", XML_NS_DAV, 1, propfind_fromhdr, NULL, "Content-Type" },
2210 { "getetag", XML_NS_DAV, 1, propfind_getetag, NULL, NULL },
2211 { "getlastmodified", XML_NS_DAV, 1, propfind_getlastmod, NULL, NULL },
2212 { "lockdiscovery", XML_NS_DAV, 1, propfind_lockdisc, NULL, NULL },
2213 { "resourcetype", XML_NS_DAV, 1,
2283 { "getcontentlength", NS_DAV, PROP_ALLPROP | PROP_RESOURCE,
2284 propfind_getlength, NULL, NULL },
2285 { "getcontenttype", NS_DAV,
2286 PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
2287 propfind_getcontenttype, NULL, "Content-Type" },
2288 { "getetag", NS_DAV, PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
2289 propfind_getetag, NULL, NULL },
2290 { "getlastmodified", NS_DAV,
2291 PROP_ALLPROP | /*PROP_COLLECTION |*/ PROP_RESOURCE,
2292 propfind_getlastmod, NULL, NULL },
2293 { "lockdiscovery", NS_DAV, PROP_ALLPROP | PROP_RESOURCE,
2294 propfind_lockdisc, NULL, NULL },
2295 { "resourcetype", NS_DAV,
2296 PROP_ALLPROP | PROP_PRINCIPAL | PROP_COLLECTION | PROP_RESOURCE,
22142297 propfind_restype, proppatch_restype, NULL },
2215 { "supportedlock", XML_NS_DAV, 1, propfind_suplock, NULL, NULL },
2298 { "supportedlock", NS_DAV, PROP_ALLPROP | PROP_RESOURCE,
2299 propfind_suplock, NULL, NULL },
22162300
22172301 /* WebDAV Versioning (RFC 3253) properties */
2218 { "supported-report-set", XML_NS_DAV, 0, propfind_reportset, NULL, NULL },
2302 { "supported-report-set", NS_DAV, PROP_PRINCIPAL | PROP_COLLECTION,
2303 propfind_reportset, NULL, NULL },
22192304
22202305 /* WebDAV ACL (RFC 3744) properties */
2221 { "alternate-URI-set", XML_NS_DAV, 0, NULL, NULL, NULL },
2222 { "principal-URL", XML_NS_DAV, 0, propfind_principalurl, NULL, NULL },
2223 { "group-member-set", XML_NS_DAV, 0, NULL, NULL, NULL },
2224 { "group-membership", XML_NS_DAV, 0, NULL, NULL, NULL },
2225 { "owner", XML_NS_DAV, 0, propfind_owner, NULL, NULL },
2226 { "group", XML_NS_DAV, 0, NULL, NULL, NULL },
2227 { "supported-privilege-set", XML_NS_DAV, 0,
2306 { "alternate-URI-set", NS_DAV, 0, NULL, NULL, NULL },
2307 { "principal-URL", NS_DAV, PROP_PRINCIPAL,
2308 propfind_principalurl, NULL, NULL },
2309 { "group-member-set", NS_DAV, 0, NULL, NULL, NULL },
2310 { "group-membership", NS_DAV, 0, NULL, NULL, NULL },
2311 { "owner", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
2312 propfind_owner, NULL, NULL },
2313 { "group", NS_DAV, 0, NULL, NULL, NULL },
2314 { "supported-privilege-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
22282315 propfind_supprivset, NULL, NULL },
2229 { "current-user-privilege-set", XML_NS_DAV, 0,
2316 { "current-user-privilege-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
22302317 propfind_curprivset, NULL, NULL },
2231 { "acl", XML_NS_DAV, 0, propfind_acl, NULL, NULL },
2232 { "acl-restrictions", XML_NS_DAV, 0, propfind_aclrestrict, NULL, NULL },
2233 { "inherited-acl-set", XML_NS_DAV, 0, NULL, NULL, NULL },
2234 { "principal-collection-set", XML_NS_DAV, 0,
2318 { "acl", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
2319 propfind_acl, NULL, NULL },
2320 { "acl-restrictions", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
2321 propfind_aclrestrict, NULL, NULL },
2322 { "inherited-acl-set", NS_DAV, 0, NULL, NULL, NULL },
2323 { "principal-collection-set", NS_DAV,
2324 PROP_ROOT | PROP_PRINCIPAL | PROP_COLLECTION | PROP_RESOURCE,
22352325 propfind_princolset, NULL, NULL },
22362326
22372327 /* WebDAV Quota (RFC 4331) properties */
2238 { "quota-available-bytes", XML_NS_DAV, 0, propfind_quota, NULL, NULL },
2239 { "quota-used-bytes", XML_NS_DAV, 0, propfind_quota, NULL, NULL },
2328 { "quota-available-bytes", NS_DAV, PROP_COLLECTION,
2329 propfind_quota, NULL, NULL },
2330 { "quota-used-bytes", NS_DAV, PROP_COLLECTION,
2331 propfind_quota, NULL, NULL },
22402332
22412333 /* WebDAV Current Principal (RFC 5397) properties */
2242 { "current-user-principal", XML_NS_DAV, 0, propfind_curprin, NULL, NULL },
2334 { "current-user-principal", NS_DAV,
2335 PROP_ROOT | PROP_PRINCIPAL | PROP_COLLECTION | PROP_RESOURCE,
2336 propfind_curprin, NULL, NULL },
22432337
22442338 /* WebDAV POST (RFC 5995) properties */
2245 { "add-member", XML_NS_DAV, 0, propfind_addmember, NULL, NULL },
2339 { "add-member", NS_DAV, PROP_COLLECTION
2340 | PROP_CALENDAR, /* Until Apple Contacts is fixed */
2341 propfind_addmember, NULL, NULL },
22462342
22472343 /* WebDAV Sync (RFC 6578) properties */
2248 { "sync-token", XML_NS_DAV, 1, propfind_sync_token, NULL, NULL },
2344 { "sync-token", NS_DAV, PROP_COLLECTION,
2345 propfind_sync_token, NULL, NULL },
22492346
22502347 /* CalDAV (RFC 4791) properties */
2251 { "calendar-data", XML_NS_CALDAV, 0, propfind_getdata, NULL, NULL },
2252 { "calendar-description", XML_NS_CALDAV, 0,
2348 { "calendar-data", NS_CALDAV, PROP_RESOURCE | PROP_CALENDAR,
2349 propfind_getdata, NULL, NULL },
2350 { "calendar-description", NS_CALDAV, PROP_COLLECTION | PROP_CALENDAR,
22532351 propfind_fromdb, proppatch_todb, NULL },
2254 { "calendar-home-set", XML_NS_CALDAV, 0, propfind_calurl, NULL, NULL },
2255 { "calendar-timezone", XML_NS_CALDAV, 0,
2352 { "calendar-home-set", NS_CALDAV, PROP_PRINCIPAL,
2353 propfind_calurl, NULL, NULL },
2354 { "calendar-timezone", NS_CALDAV, PROP_COLLECTION | PROP_CALENDAR,
2355 propfind_fromdb, proppatch_timezone, NULL },
2356 { "supported-calendar-component-set", NS_CALDAV,
2357 PROP_COLLECTION | PROP_CALENDAR,
2358 propfind_calcompset, proppatch_calcompset, NULL },
2359 { "supported-calendar-data", NS_CALDAV, PROP_COLLECTION | PROP_CALENDAR,
2360 propfind_suppcaldata, NULL, NULL },
2361 { "max-resource-size", NS_CALDAV, 0, NULL, NULL, NULL },
2362 { "min-date-time", NS_CALDAV, 0, NULL, NULL, NULL },
2363 { "max-date-time", NS_CALDAV, 0, NULL, NULL, NULL },
2364 { "max-instances", NS_CALDAV, 0, NULL, NULL, NULL },
2365 { "max-attendees-per-instance", NS_CALDAV, 0, NULL, NULL, NULL },
2366
2367 /* CalDAV Scheduling (RFC 6638) properties */
2368 { "schedule-tag", NS_CALDAV, PROP_RESOURCE | PROP_CALENDAR,
2369 propfind_schedtag, NULL, NULL },
2370 { "schedule-inbox-URL", NS_CALDAV, PROP_PRINCIPAL,
2371 propfind_calurl, NULL, SCHED_INBOX },
2372 { "schedule-outbox-URL", NS_CALDAV, PROP_PRINCIPAL,
2373 propfind_calurl, NULL, SCHED_OUTBOX },
2374 { "schedule-default-calendar-URL", NS_CALDAV, PROP_COLLECTION,
2375 propfind_calurl, NULL, SCHED_DEFAULT },
2376 { "schedule-calendar-transp", NS_CALDAV,
2377 PROP_COLLECTION | PROP_CALENDAR,
2378 propfind_caltransp, proppatch_caltransp, NULL },
2379 { "calendar-user-address-set", NS_CALDAV, PROP_PRINCIPAL,
2380 propfind_caluseraddr, NULL, NULL },
2381 { "calendar-user-type", NS_CALDAV, 0, NULL, NULL, NULL },
2382
2383 /* CardDAV (RFC 6352) properties */
2384 { "address-data", NS_CARDDAV, PROP_RESOURCE | PROP_ADDRESSBOOK,
2385 propfind_getdata, NULL, NULL },
2386 { "addressbook-description", NS_CARDDAV,
2387 PROP_COLLECTION | PROP_ADDRESSBOOK,
22562388 propfind_fromdb, proppatch_todb, NULL },
2257 { "supported-calendar-component-set", XML_NS_CALDAV, 0,
2258 propfind_calcompset, proppatch_calcompset, NULL },
2259 { "supported-calendar-data", XML_NS_CALDAV, 0,
2260 propfind_suppcaldata, NULL, NULL },
2261 { "max-resource-size", XML_NS_CALDAV, 0, NULL, NULL, NULL },
2262 { "min-date-time", XML_NS_CALDAV, 0, NULL, NULL, NULL },
2263 { "max-date-time", XML_NS_CALDAV, 0, NULL, NULL, NULL },
2264 { "max-instances", XML_NS_CALDAV, 0, NULL, NULL, NULL },
2265 { "max-attendees-per-instance", XML_NS_CALDAV, 0, NULL, NULL, NULL },
2266
2267 /* CalDAV Scheduling (RFC 6638) properties */
2268 { "schedule-tag", XML_NS_CALDAV, 0, propfind_schedtag, NULL, NULL },
2269 { "schedule-inbox-URL", XML_NS_CALDAV, 0,
2270 propfind_calurl, NULL, SCHED_INBOX },
2271 { "schedule-outbox-URL", XML_NS_CALDAV, 0,
2272 propfind_calurl, NULL, SCHED_OUTBOX },
2273 { "schedule-default-calendar-URL", XML_NS_CALDAV, 0,
2274 propfind_calurl, NULL, SCHED_DEFAULT },
2275 { "schedule-calendar-transp", XML_NS_CALDAV, 0,
2276 propfind_caltransp, proppatch_caltransp, NULL },
2277 { "calendar-user-address-set", XML_NS_CALDAV, 0,
2278 propfind_caluseraddr, NULL, NULL },
2279 { "calendar-user-type", XML_NS_CALDAV, 0, NULL, NULL, NULL },
2280
2281 /* CardDAV (RFC 6352) properties */
2282 { "address-data", XML_NS_CARDDAV, 0, propfind_getdata, NULL, NULL },
2283 { "addressbook-description", XML_NS_CARDDAV, 0,
2284 propfind_fromdb, proppatch_todb, NULL },
2285 { "addressbook-home-set", XML_NS_CARDDAV, 0,
2389 { "addressbook-home-set", NS_CARDDAV, PROP_PRINCIPAL,
22862390 propfind_abookurl, NULL, NULL },
2287 { "supported-address-data", XML_NS_CARDDAV, 0,
2391 { "supported-address-data", NS_CARDDAV,
2392 PROP_COLLECTION | PROP_ADDRESSBOOK,
22882393 propfind_suppaddrdata, NULL, NULL },
2289 { "max-resource-size", XML_NS_CARDDAV, 0, NULL, NULL, NULL },
2394 { "max-resource-size", NS_CARDDAV, 0, NULL, NULL, NULL },
22902395
22912396 /* Apple Calendar Server properties */
2292 { "getctag", XML_NS_CS, 1, propfind_sync_token, NULL, NULL },
2293
2294 /* Apple iCal properties */
2295 { "calendar-color", XML_NS_ICAL, 0,
2296 propfind_fromdb, proppatch_todb, NULL },
2297 { "calendar-order", XML_NS_ICAL, 0,
2298 propfind_fromdb, proppatch_todb, NULL },
2299
2300 { NULL, NULL, 0, NULL, NULL, NULL }
2397 { "getctag", NS_CS, PROP_ALLPROP | PROP_COLLECTION,
2398 propfind_sync_token, NULL, NULL },
2399
2400 { NULL, 0, 0, NULL, NULL, NULL }
23012401 };
2402
2403
2404 /* annotemore_findall callback for adding dead properties (allprop/propname) */
2405 static int allprop_cb(const char *mailbox __attribute__((unused)),
2406 const char *entry,
2407 const char *userid, struct annotation_data *attrib,
2408 void *rock)
2409 {
2410 struct allprop_rock *arock = (struct allprop_rock *) rock;
2411 const struct prop_entry *pentry;
2412 char *href, *name;
2413 xmlNsPtr ns;
2414 xmlNodePtr node;
2415
2416 /* Make sure its a shared entry or the user's private one */
2417 if (*userid && strcmp(userid, arock->fctx->userid)) return 0;
2418
2419 /* Split entry into namespace href and name ( <href>name ) */
2420 buf_setcstr(&arock->fctx->buf, entry + strlen(ANNOT_NS) + 1);
2421 href = (char *) buf_cstring(&arock->fctx->buf);
2422 if ((name = strchr(href, '>'))) *name++ = '\0';
2423
2424 /* Look for a match against live properties */
2425 for (pentry = prop_entries;
2426 pentry->name &&
2427 (strcmp(name, pentry->name) ||
2428 strcmp(href, known_namespaces[pentry->ns].href));
2429 pentry++);
2430
2431 if (pentry->name &&
2432 (arock->fctx->mode == PROPFIND_ALL /* Skip all live properties */
2433 || (pentry->flags & PROP_ALLPROP))) /* Skip those already included */
2434 return 0;
2435
2436 /* Look for an instance of this namespace in our response */
2437 ns = hash_lookup(href, arock->fctx->ns_table);
2438 if (!ns) {
2439 char prefix[5];
2440 snprintf(prefix, sizeof(prefix), "X%u", arock->fctx->prefix_count++);
2441 ns = xmlNewNs(arock->fctx->root, BAD_CAST href, BAD_CAST prefix);
2442 hash_insert(href, ns, arock->fctx->ns_table);
2443 }
2444
2445 /* Add the dead property to the response */
2446 node = xml_add_prop(HTTP_OK, arock->fctx->ns[NS_DAV],
2447 &arock->propstat[PROPSTAT_OK],
2448 BAD_CAST name, ns, NULL, 0);
2449
2450 if (arock->fctx->mode == PROPFIND_ALL) {
2451 xmlAddChild(node, xmlNewCDataBlock(arock->fctx->root->doc,
2452 BAD_CAST attrib->value,
2453 attrib->size));
2454 }
2455
2456 return 0;
2457 }
2458
2459
2460 static int prescreen_prop(const struct prop_entry *entry,
2461 xmlNodePtr prop,
2462 struct propfind_ctx *fctx)
2463 {
2464 xmlChar *type = NULL, *ver = NULL;
2465 unsigned allowed = 1;
2466
2467 switch (fctx->req_tgt->namespace) {
2468 case URL_NS_PRINCIPAL:
2469 if (!(entry->flags & PROP_PRINCIPAL)) allowed = 0;
2470 break;
2471
2472 case URL_NS_CALENDAR:
2473 if ((entry->flags & PROP_ADDRESSBOOK) ||
2474 (fctx->req_tgt->resource &&
2475 !(entry->flags & PROP_RESOURCE))) {
2476 allowed = 0;
2477 }
2478
2479 /* Sanity check any calendar-data "property" request */
2480 else if (prop && !xmlStrcmp(prop->name, BAD_CAST "calendar-data") &&
2481 (((type = xmlGetProp(prop, BAD_CAST "content-type"))
2482 && xmlStrcmp(type, BAD_CAST "text/calendar")) ||
2483 ((ver = xmlGetProp(prop, BAD_CAST "version")) &&
2484 xmlStrcmp(ver, BAD_CAST "2.0")))) {
2485 allowed = 0;
2486 fctx->err->precond = CALDAV_SUPP_DATA;
2487 *fctx->ret = HTTP_FORBIDDEN;
2488 }
2489 break;
2490
2491 case URL_NS_ADDRESSBOOK:
2492 if ((entry->flags & PROP_CALENDAR) ||
2493 (fctx->req_tgt->resource &&
2494 !(entry->flags & PROP_RESOURCE))) {
2495 allowed = 0;
2496 }
2497
2498 /* Sanity check any address-data "property" request */
2499 else if (prop && !xmlStrcmp(prop->name, BAD_CAST "address-data") &&
2500 (((type = xmlGetProp(prop, BAD_CAST "content-type"))
2501 && xmlStrcmp(type, BAD_CAST "text/vcard")) ||
2502 ((ver = xmlGetProp(prop, BAD_CAST "version")) &&
2503 xmlStrcmp(ver, BAD_CAST "3.0")))) {
2504 allowed = 0;
2505 fctx->err->precond = CALDAV_SUPP_DATA;
2506 *fctx->ret = HTTP_FORBIDDEN;
2507 }
2508 break;
2509
2510 default:
2511 if (!(entry->flags & PROP_ROOT)) allowed = 0;
2512 break;
2513 }
2514
2515 if (type) xmlFree(type);
2516 if (ver) xmlFree(ver);
2517
2518 return allowed;
2519 }
23022520
23032521
23042522 /* Parse the requested properties and create a linked list of fetch callbacks.
23062524 */
23072525 static int preload_proplist(xmlNodePtr proplist, struct propfind_ctx *fctx)
23082526 {
2527 int ret = 0;
23092528 xmlNodePtr prop;
23102529 const struct prop_entry *entry;
23112530
2531 if (fctx->mode == PROPFIND_ALL || fctx->mode == PROPFIND_NAME) {
2532 xmlNsPtr nsDef;
2533
2534 /* Add live properties for allprop/propname */
2535 for (entry = prop_entries; entry->name; entry++) {
2536 if (entry->flags & PROP_ALLPROP) {
2537 /* Pre-screen request based on prop flags */
2538 int allowed = prescreen_prop(entry, NULL, fctx);
2539
2540 if (allowed || fctx->mode == PROP_ALLPROP) {
2541 struct propfind_entry_list *nentry =
2542 xzmalloc(sizeof(struct propfind_entry_list));
2543
2544 ensure_ns(fctx->ns, entry->ns, fctx->root,
2545 known_namespaces[entry->ns].href,
2546 known_namespaces[entry->ns].prefix);
2547
2548 nentry->name = BAD_CAST entry->name;
2549 nentry->ns = fctx->ns[entry->ns];
2550 if (allowed) {
2551 nentry->flags = entry->flags;
2552 nentry->get = entry->get;
2553 nentry->rock = entry->rock;
2554 }
2555 nentry->next = fctx->elist;
2556 fctx->elist = nentry;
2557 }
2558 }
2559 }
2560
2561 /* Add all namespaces attached to the response to our hash table */
2562 construct_hash_table(fctx->ns_table, 10, 1);
2563
2564 for (nsDef = fctx->root->nsDef; nsDef; nsDef = nsDef->next) {
2565 hash_insert((const char *) nsDef->href, nsDef, fctx->ns_table);
2566 }
2567 }
2568
23122569 /* Iterate through requested properties */
2313 for (prop = proplist; prop; prop = prop->next) {
2570 for (prop = proplist; !*fctx->ret && prop; prop = prop->next) {
23142571 if (prop->type == XML_ELEMENT_NODE) {
2315 struct propfind_entry_list *nentry =
2316 xzmalloc(sizeof(struct propfind_entry_list));
2572 struct propfind_entry_list *nentry;
23172573
23182574 /* Look for a match against our known properties */
23192575 for (entry = prop_entries;
23202576 entry->name &&
23212577 (strcmp((const char *) prop->name, entry->name) ||
2322 strcmp((const char *) prop->ns->href, entry->ns));
2578 strcmp((const char *) prop->ns->href,
2579 known_namespaces[entry->ns].href));
23232580 entry++);
23242581
2325 nentry->prop = prop;
2582 /* Skip properties already included by allprop */
2583 if (fctx->mode == PROPFIND_ALL && (entry->flags & PROP_ALLPROP))
2584 continue;
2585
2586 nentry = xzmalloc(sizeof(struct propfind_entry_list));
2587 nentry->name = prop->name;
2588 nentry->ns = prop->ns;
23262589 if (entry->name) {
2327 /* Found a match */
2328 nentry->get = entry->get;
2329 nentry->rock = entry->rock;
2590 /* Found a match - Pre-screen request based on prop flags */
2591 if (prescreen_prop(entry, prop, fctx)) {
2592 nentry->flags = entry->flags;
2593 nentry->get = entry->get;
2594 nentry->rock = entry->rock;
2595 }
23302596 }
23312597 else {
23322598 /* No match, treat as a dead property */
2599 nentry->flags = PROP_COLLECTION;
23332600 nentry->get = propfind_fromdb;
23342601 nentry->rock = NULL;
23352602 }
23382605 }
23392606 }
23402607
2341 return 0;
2608 return ret;
23422609 }
23432610
23442611
23612628 !xmlStrcmp(instr->name, BAD_CAST "remove")) set = 0;
23622629 else {
23632630 syslog(LOG_INFO, "Unknown PROPPATCH instruction");
2364 *pctx->errstr = "Unknown PROPPATCH instruction";
2631 pctx->err->desc = "Unknown PROPPATCH instruction";
23652632 return HTTP_BAD_REQUEST;
23662633 }
23672634
23692636 for (prop = instr->children;
23702637 prop && prop->type != XML_ELEMENT_NODE; prop = prop->next);
23712638 if (!prop || xmlStrcmp(prop->name, BAD_CAST "prop")) {
2372 *pctx->errstr = "Missing prop element";
2639 pctx->err->desc = "Missing prop element";
23732640 return HTTP_BAD_REQUEST;
23742641 }
23752642
23822649 for (entry = prop_entries;
23832650 entry->name &&
23842651 (strcmp((const char *) prop->name, entry->name) ||
2385 strcmp((const char *) prop->ns->href, entry->ns));
2652 strcmp((const char *) prop->ns->href,
2653 known_namespaces[entry->ns].href));
23862654 entry++);
23872655
23882656 if (entry->name) {
23902658 /* Protected property */
23912659 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
23922660 &propstat[PROPSTAT_FORBID],
2393 prop, NULL,
2661 prop->name, prop->ns, NULL,
23942662 DAV_PROT_PROP);
23952663 *pctx->ret = HTTP_FORBIDDEN;
23962664 }
25562824 /* Remote mailbox */
25572825 struct backend *be;
25582826
2559 be = proxy_findserver(server, &http_protocol, httpd_userid,
2827 be = proxy_findserver(server, &http_protocol, proxy_userid,
25602828 &backend_cached, NULL, NULL, httpd_in);
25612829 if (!be) return HTTP_UNAVAILABLE;
25622830
26482916 }
26492917
26502918 if (!xmlStrcmp(prin->name, BAD_CAST "self")) {
2651 userid = httpd_userid;
2919 userid = proxy_userid;
26522920 }
26532921 #if 0 /* XXX Do we need to support this? */
26542922 else if (!xmlStrcmp(prin->name, BAD_CAST "owner")) {
28883156
28893157 if (server) {
28903158 /* Remote source mailbox */
2891 src_be = proxy_findserver(server, &http_protocol, httpd_userid,
3159 src_be = proxy_findserver(server, &http_protocol, proxy_userid,
28923160 &backend_cached, NULL, NULL, httpd_in);
28933161 if (!src_be) return HTTP_UNAVAILABLE;
28943162 }
29193187
29203188 if (server) {
29213189 /* Remote destination mailbox */
2922 dest_be = proxy_findserver(server, &http_protocol, httpd_userid,
3190 dest_be = proxy_findserver(server, &http_protocol, proxy_userid,
29233191 &backend_cached, NULL, NULL, httpd_in);
29243192 if (!dest_be) return HTTP_UNAVAILABLE;
29253193 }
29483216 if (!*cparams->davdb.db) {
29493217 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
29503218 "Check 'configdirectory' permissions or "
2951 "'proxyservers' option on backend server.", httpd_userid);
3219 "'proxyservers' option on backend server.", proxy_userid);
29523220 txn->error.desc = "DAV database is not opened";
29533221 return HTTP_SERVER_ERROR;
29543222 }
30183286 }
30193287
30203288 /* Check any preconditions on source */
3021 precond = check_precond(txn, (void **) ddata, etag, lastmod, 0);
3289 precond = check_precond(txn, (void **) ddata, etag, lastmod);
30223290
30233291 switch (precond) {
30243292 case HTTP_OK:
31373405 /* Remote mailbox */
31383406 struct backend *be;
31393407
3140 be = proxy_findserver(server, &http_protocol, httpd_userid,
3408 be = proxy_findserver(server, &http_protocol, proxy_userid,
31413409 &backend_cached, NULL, NULL, httpd_in);
31423410 if (!be) return HTTP_UNAVAILABLE;
31433411
31493417 if (!*dparams->davdb.db) {
31503418 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
31513419 "Check 'configdirectory' permissions or "
3152 "'proxyservers' option on backend server.", httpd_userid);
3420 "'proxyservers' option on backend server.", proxy_userid);
31533421 txn->error.desc = "DAV database is not opened";
31543422 return HTTP_SERVER_ERROR;
31553423 }
32133481 }
32143482
32153483 /* Check any preconditions */
3216 precond = dparams->check_precond(txn, (void *) ddata, etag, lastmod, 0);
3484 precond = dparams->check_precond(txn, (void *) ddata, etag, lastmod);
32173485
32183486 switch (precond) {
32193487 case HTTP_OK:
33043572 /* Remote mailbox */
33053573 struct backend *be;
33063574
3307 be = proxy_findserver(server, &http_protocol, httpd_userid,
3575 be = proxy_findserver(server, &http_protocol, proxy_userid,
33083576 &backend_cached, NULL, NULL, httpd_in);
33093577 if (!be) return HTTP_UNAVAILABLE;
33103578
33163584 if (!*gparams->davdb.db) {
33173585 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
33183586 "Check 'configdirectory' permissions or "
3319 "'proxyservers' option on backend server.", httpd_userid);
3587 "'proxyservers' option on backend server.", proxy_userid);
33203588 txn->error.desc = "DAV database is not opened";
33213589 return HTTP_SERVER_ERROR;
33223590 }
33523620 offset = record.header_size;
33533621 datalen = record.size - offset;
33543622
3355 txn->flags.ranges = !txn->flags.ce;
3623 txn->flags.ranges = 1;
33563624 etag = message_guid_encode(&record.guid);
33573625 lastmod = record.internaldate;
33583626 }
33653633 }
33663634
33673635 /* Check any preconditions, including range request */
3368 precond = gparams->check_precond(txn, (void *) ddata, etag, lastmod,
3369 datalen);
3636 precond = gparams->check_precond(txn, (void *) ddata, etag, lastmod);
33703637
33713638 switch (precond) {
33723639 case HTTP_OK:
3373 break;
3374
33753640 case HTTP_PARTIAL:
3376 /* Set data parameters for range */
3377 offset += resp_body->range->first;
3378 datalen = resp_body->range->last - resp_body->range->first + 1;
3379 break;
3380
33813641 case HTTP_NOT_MODIFIED:
3382 /* Fill in ETag for 304 response */
3642 /* Fill in ETag, Last-Modified, Expires, and Cache-Control */
33833643 resp_body->etag = etag;
3644 resp_body->lastmod = lastmod;
3645 resp_body->maxage = 3600; /* 1 hr */
3646 txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; /* don't use stale data */
3647
3648 if (precond != HTTP_NOT_MODIFIED) break;
33843649
33853650 default:
33863651 /* We failed a precondition - don't perform the request */
33883653 goto done;
33893654 }
33903655
3391 /* Fill in Last-Modified, ETag, and Content-Type */
3392 resp_body->lastmod = lastmod;
3393 resp_body->etag = etag;
3394
33953656 if (record.uid) {
33963657 resp_body->type = gparams->content_type;
33973658
34063667 }
34073668 }
34083669
3409 if (resp_body->range && resp_body->range->next) {
3410 /* multiple ranges */
3411 multipart_byteranges(txn, msg_base + record.header_size);
3412 }
3413 else write_body(precond, txn, data, datalen);
3670 write_body(precond, txn, data, datalen);
34143671
34153672 if (msg_base)
34163673 mailbox_unmap_message(mailbox, record.uid, &msg_base, &msg_size);
34853742 /* Remote mailbox */
34863743 struct backend *be;
34873744
3488 be = proxy_findserver(server, &http_protocol, httpd_userid,
3745 be = proxy_findserver(server, &http_protocol, proxy_userid,
34893746 &backend_cached, NULL, NULL, httpd_in);
34903747 if (!be) return HTTP_UNAVAILABLE;
34913748
34973754 if (!*lparams->davdb.db) {
34983755 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
34993756 "Check 'configdirectory' permissions or "
3500 "'proxyservers' option on backend server.", httpd_userid);
3757 "'proxyservers' option on backend server.", proxy_userid);
35013758 txn->error.desc = "DAV database is not opened";
35023759 return HTTP_SERVER_ERROR;
35033760 }
35453802 }
35463803
35473804 /* Check any preconditions */
3548 precond = lparams->check_precond(txn, ddata, etag, lastmod, 0);
3805 precond = lparams->check_precond(txn, ddata, etag, lastmod);
35493806
35503807 switch (precond) {
35513808 case HTTP_OK:
37423999 p = strchr(server, '!');
37434000 if (p) *p++ = '\0';
37444001
3745 be = proxy_findserver(server, &http_protocol, httpd_userid,
4002 be = proxy_findserver(server, &http_protocol, proxy_userid,
37464003 &backend_cached, NULL, NULL, httpd_in);
37474004 if (!be) return HTTP_UNAVAILABLE;
37484005
37934050 pctx.root = root;
37944051 pctx.ns = ns;
37954052 pctx.tid = NULL;
3796 pctx.errstr = &txn->error.desc;
4053 pctx.err = &txn->error;
37974054 pctx.ret = &r;
37984055
37994056 /* Execute the property patch instructions */
39384195 if ((r = mboxlist_lookup(mboxname, &mbentry, NULL))) {
39394196 syslog(LOG_INFO, "mboxlist_lookup(%s) failed: %s",
39404197 mboxname, error_message(r));
3941 *fctx->errstr = error_message(r);
4198 fctx->err->desc = error_message(r);
39424199 *fctx->ret = HTTP_SERVER_ERROR;
39434200 goto done;
39444201 }
39504207 if ((r = mailbox_open_irl(mboxname, &mailbox))) {
39514208 syslog(LOG_INFO, "mailbox_open_irl(%s) failed: %s",
39524209 mboxname, error_message(r));
3953 *fctx->errstr = error_message(r);
4210 fctx->err->desc = error_message(r);
39544211 *fctx->ret = HTTP_SERVER_ERROR;
39554212 goto done;
39564213 }
40254282 const char **hdr;
40264283 unsigned depth;
40274284 xmlDocPtr indoc = NULL, outdoc = NULL;
4028 xmlNodePtr root, cur = NULL;
4285 xmlNodePtr root, cur = NULL, props = NULL;
40294286 xmlNsPtr ns[NUM_NAMESPACE];
4287 struct hash_table ns_table = { 0, NULL, NULL };
40304288 struct propfind_ctx fctx;
40314289 struct propfind_entry_list *elist = NULL;
40324290
40824340 /* Remote mailbox */
40834341 struct backend *be;
40844342
4085 be = proxy_findserver(server, &http_protocol, httpd_userid,
4343 be = proxy_findserver(server, &http_protocol, proxy_userid,
40864344 &backend_cached, NULL, NULL, httpd_in);
40874345 if (!be) return HTTP_UNAVAILABLE;
40884346
40934351 if (!*fparams->davdb.db) {
40944352 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
40954353 "Check 'configdirectory' permissions or "
4096 "'proxyservers' option on backend server.", httpd_userid);
4354 "'proxyservers' option on backend server.", proxy_userid);
40974355 txn->error.desc = "DAV database is not opened";
40984356 return HTTP_SERVER_ERROR;
40994357 }
41124370 if (ret) goto done;
41134371
41144372 if (!root) {
4115 /* XXX allprop request */
4373 /* Empty request */
4374 fctx.mode = PROPFIND_ALL;
41164375 }
41174376 else {
41184377 indoc = root->doc;
4119
4120 /* XXX Need to support propname request too! */
41214378
41224379 /* Make sure its a propfind element */
41234380 if (xmlStrcmp(root->name, BAD_CAST "propfind")) {
41344391 spool_cache_header(xstrdup(":type"), xstrdup((const char *) cur->name),
41354392 txn->req_hdrs);
41364393
4137 /* Make sure its a prop element */
4138 /* XXX TODO: Check for allprop and propname too */
4139 if (!cur || xmlStrcmp(cur->name, BAD_CAST "prop")) {
4394 /* Make sure its a known element */
4395 if (!cur) {
4396 ret = HTTP_BAD_REQUEST;
4397 goto done;
4398 }
4399 else if (!xmlStrcmp(cur->name, BAD_CAST "allprop")) {
4400 fctx.mode = PROPFIND_ALL;
4401
4402 /* Check for 'include' element */
4403 for (cur = cur->next;
4404 cur && cur->type != XML_ELEMENT_NODE; cur = cur->next);
4405
4406 if (cur && !xmlStrcmp(cur->name, BAD_CAST "include")) {
4407 props = cur->children;
4408 }
4409 }
4410 else if (!xmlStrcmp(cur->name, BAD_CAST "propname")) {
4411 fctx.mode = PROPFIND_NAME;
4412 fctx.prefer = PREFER_MIN; /* Don't want 404 (Not Found) */
4413 }
4414 else if (!xmlStrcmp(cur->name, BAD_CAST "prop")) {
4415 fctx.mode = PROPFIND_PROP;
4416 props = cur->children;
4417 }
4418 else {
41404419 ret = HTTP_BAD_REQUEST;
41414420 goto done;
41424421 }
41544433 /* Populate our propfind context */
41554434 fctx.req_tgt = &txn->req_tgt;
41564435 fctx.depth = depth;
4157 fctx.prefer = get_preferences(txn);
4158 fctx.userid = httpd_userid;
4436 fctx.prefer |= get_preferences(txn);
4437 fctx.userid = proxy_userid;
4438 fctx.int_userid = httpd_userid;
41594439 fctx.userisadmin = httpd_userisadmin;
41604440 fctx.authstate = httpd_authstate;
41614441 fctx.mailbox = NULL;
41724452 fctx.elist = NULL;
41734453 fctx.root = root;
41744454 fctx.ns = ns;
4175 fctx.errstr = &txn->error.desc;
4455 fctx.ns_table = &ns_table;
4456 fctx.err = &txn->error;
41764457 fctx.ret = &ret;
41774458 fctx.fetcheddata = 0;
41784459
41794460 /* Parse the list of properties and build a list of callbacks */
4180 preload_proplist(cur->children, &fctx);
4461 preload_proplist(props, &fctx);
41814462
41824463 if (!txn->req_tgt.collection &&
41834464 (!depth || !(fctx.prefer & PREFER_NOROOT))) {
42384519
42394520 buf_free(&fctx.buf);
42404521
4522 free_hash_table(&ns_table, NULL);
4523
42414524 if (outdoc) xmlFreeDoc(outdoc);
42424525 if (indoc) xmlFreeDoc(indoc);
42434526
43044587 /* Remote mailbox */
43054588 struct backend *be;
43064589
4307 be = proxy_findserver(server, &http_protocol, httpd_userid,
4590 be = proxy_findserver(server, &http_protocol, proxy_userid,
43084591 &backend_cached, NULL, NULL, httpd_in);
43094592 if (!be) return HTTP_UNAVAILABLE;
43104593
43524635 pctx.root = resp;
43534636 pctx.ns = ns;
43544637 pctx.tid = NULL;
4355 pctx.errstr = &txn->error.desc;
4638 pctx.err = &txn->error;
43564639 pctx.ret = &r;
43574640
43584641 /* Execute the property patch instructions */
43924675 static unsigned post_count = 0;
43934676 int r, ret;
43944677 size_t len;
4395 char *p;
43964678
43974679 /* Response should not be cached */
43984680 txn->flags.cc |= CC_NOCACHE;
44144696
44154697 /* Append a unique resource name to URL path and perform a PUT */
44164698 len = strlen(txn->req_tgt.path);
4417 p = txn->req_tgt.path + len;
4418
4419 snprintf(p, MAX_MAILBOX_PATH - len, "%x-%d-%ld-%u.ics",
4420 strhash(txn->req_tgt.path), getpid(), time(0), post_count++);
4699 txn->req_tgt.resource = txn->req_tgt.path + len;
4700 txn->req_tgt.reslen =
4701 snprintf(txn->req_tgt.resource, MAX_MAILBOX_PATH - len,
4702 "%x-%d-%ld-%u.ics",
4703 strhash(txn->req_tgt.path), getpid(), time(0), post_count++);
44214704
44224705 /* Tell client where to find the new resource */
44234706 txn->location = txn->req_tgt.path;
44484731 uquota_t size = 0;
44494732 unsigned flags = 0;
44504733
4451 /* Response should not be cached */
4452 txn->flags.cc |= CC_NOCACHE;
4453
4454 /* Parse the path */
4455 if ((r = pparams->parse_path(txn->req_uri->path,
4456 &txn->req_tgt, &txn->error.desc))) return r;
4457
4458 /* Make sure method is allowed (only allowed on resources) */
4459 if (!(txn->req_tgt.allow & ALLOW_WRITE)) return HTTP_NOT_ALLOWED;
4734 if (txn->meth == METH_PUT) {
4735 /* Response should not be cached */
4736 txn->flags.cc |= CC_NOCACHE;
4737
4738 /* Parse the path */
4739 if ((r = pparams->parse_path(txn->req_uri->path,
4740 &txn->req_tgt, &txn->error.desc))) {
4741 return r;
4742 }
4743
4744 /* Make sure method is allowed (only allowed on resources) */
4745 if (!(txn->req_tgt.allow & ALLOW_WRITE)) return HTTP_NOT_ALLOWED;
4746 }
44604747
44614748 /* Make sure Content-Range isn't specified */
44624749 if (spool_getheader(txn->req_hdrs, "Content-Range"))
44974784 /* Remote mailbox */
44984785 struct backend *be;
44994786
4500 be = proxy_findserver(server, &http_protocol, httpd_userid,
4787 be = proxy_findserver(server, &http_protocol, proxy_userid,
45014788 &backend_cached, NULL, NULL, httpd_in);
45024789 if (!be) return HTTP_UNAVAILABLE;
45034790
45094796 if (!*pparams->davdb.db) {
45104797 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
45114798 "Check 'configdirectory' permissions or "
4512 "'proxyservers' option on backend server.", httpd_userid);
4799 "'proxyservers' option on backend server.", proxy_userid);
45134800 txn->error.desc = "DAV database is not opened";
45144801 return HTTP_SERVER_ERROR;
45154802 }
45574844 mailbox_unlock_index(mailbox, NULL);
45584845
45594846 /* Check any preconditions */
4560 precond = pparams->check_precond(txn, ddata, etag, lastmod, 0);
4847 precond = pparams->check_precond(txn, ddata, etag, lastmod);
45614848
45624849 switch (precond) {
45634850 case HTTP_OK:
46744961 else if (!xmlStrcmp(node->name, BAD_CAST "sync-level") &&
46754962 (str = xmlNodeListGetString(inroot->doc, node->children, 1))) {
46764963 if (!strcmp((char *) str, "infinity")) {
4677 *fctx->errstr =
4964 fctx->err->desc =
46784965 "This server DOES NOT support infinite depth requests";
46794966 ret = HTTP_SERVER_ERROR;
46804967 }
46814968 else if ((sscanf((char *) str, "%u", &fctx->depth) != 1) ||
46824969 (fctx->depth != 1)) {
4683 *fctx->errstr = "Illegal sync-level";
4970 fctx->err->desc = "Illegal sync-level";
46844971 ret = HTTP_BAD_REQUEST;
46854972 }
46864973 }
47044991
47054992 /* Check Depth */
47064993 if (!fctx->depth) {
4707 *fctx->errstr = "Illegal sync-level";
4994 fctx->err->desc = "Illegal sync-level";
47084995 ret = HTTP_BAD_REQUEST;
47094996 goto done;
47104997 }
47585045
47595046 if (!nresp) {
47605047 /* DAV:number-of-matches-within-limits */
4761 *fctx->errstr = "Unable to truncate results";
5048 fctx->err->desc = "Unable to truncate results";
47625049 ret = HTTP_FORBIDDEN; /* HTTP_NO_STORAGE ? */
47635050 goto done;
47645051 }
48265113 int ret = 0, r;
48275114 const char **hdr;
48285115 unsigned depth = 0;
4829 xmlNodePtr inroot = NULL, outroot = NULL, cur, prop = NULL;
5116 xmlNodePtr inroot = NULL, outroot = NULL, cur, props = NULL;
48305117 const struct report_type_t *report = NULL;
48315118 xmlNsPtr ns[NUM_NAMESPACE];
5119 struct hash_table ns_table = { 0, NULL, NULL };
48325120 struct propfind_ctx fctx;
48335121 struct propfind_entry_list *elist = NULL;
48345122
49185206 /* Remote mailbox */
49195207 struct backend *be;
49205208
4921 be = proxy_findserver(server, &http_protocol, httpd_userid,
5209 be = proxy_findserver(server, &http_protocol, proxy_userid,
49225210 &backend_cached, NULL, NULL, httpd_in);
49235211 if (!be) ret = HTTP_UNAVAILABLE;
49245212 else ret = http_pipe_req_resp(be, txn);
49295217 if (!*rparams->davdb.db) {
49305218 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
49315219 "Check 'configdirectory' permissions or "
4932 "'proxyservers' option on backend server.", httpd_userid);
5220 "'proxyservers' option on backend server.", proxy_userid);
49335221 txn->error.desc = "DAV database is not opened";
49345222 return HTTP_SERVER_ERROR;
49355223 }
49415229 for (cur = inroot->children; cur; cur = cur->next) {
49425230 if (cur->type == XML_ELEMENT_NODE) {
49435231 if (!xmlStrcmp(cur->name, BAD_CAST "allprop")) {
4944 syslog(LOG_WARNING, "REPORT %s w/allprop", report->name);
4945 txn->error.desc = "Unsupported REPORT option <allprop>\r\n";
4946 ret = HTTP_NOT_IMPLEMENTED;
4947 goto done;
5232 fctx.mode = PROPFIND_ALL;
5233 break;
49485234 }
49495235 else if (!xmlStrcmp(cur->name, BAD_CAST "propname")) {
4950 syslog(LOG_WARNING, "REPORT %s w/propname", report->name);
4951 txn->error.desc = "Unsupported REPORT option <propname>\r\n";
4952 ret = HTTP_NOT_IMPLEMENTED;
4953 goto done;
5236 fctx.mode = PROPFIND_NAME;
5237 fctx.prefer = PREFER_MIN; /* Don't want 404 (Not Found) */
5238 break;
49545239 }
49555240 else if (!xmlStrcmp(cur->name, BAD_CAST "prop")) {
4956 prop = cur;
5241 fctx.mode = PROPFIND_PROP;
5242 props = cur->children;
49575243 break;
49585244 }
49595245 }
49605246 }
49615247
4962 if (!prop && (report->flags & REPORT_NEED_PROPS)) {
5248 if (!props && (report->flags & REPORT_NEED_PROPS)) {
49635249 txn->error.desc = "Missing <prop> element in REPORT\r\n";
49645250 ret = HTTP_BAD_REQUEST;
49655251 goto done;
49765262 /* Populate our propfind context */
49775263 fctx.req_tgt = &txn->req_tgt;
49785264 fctx.depth = depth;
4979 fctx.prefer = get_preferences(txn);
4980 fctx.userid = httpd_userid;
5265 fctx.prefer |= get_preferences(txn);
5266 fctx.userid = proxy_userid;
5267 fctx.int_userid = httpd_userid;
49815268 fctx.userisadmin = httpd_userisadmin;
49825269 fctx.authstate = httpd_authstate;
49835270 fctx.mailbox = NULL;
49865273 fctx.elist = NULL;
49875274 fctx.root = outroot;
49885275 fctx.ns = ns;
4989 fctx.errstr = &txn->error.desc;
5276 fctx.ns_table = &ns_table;
5277 fctx.err = &txn->error;
49905278 fctx.ret = &ret;
49915279 fctx.fetcheddata = 0;
49925280
49935281 /* Parse the list of properties and build a list of callbacks */
4994 if (prop) preload_proplist(prop->children, &fctx);
5282 if (fctx.mode) ret = preload_proplist(props, &fctx);
49955283
49965284 /* Process the requested report */
4997 ret = (*report->proc)(txn, inroot, &fctx);
5285 if (!ret) ret = (*report->proc)(txn, inroot, &fctx);
49985286
49995287 /* Output the XML response */
50005288 if (!ret && outroot) {
50145302 }
50155303
50165304 buf_free(&fctx.buf);
5305
5306 free_hash_table(&ns_table, NULL);
50175307
50185308 if (inroot) xmlFreeDoc(inroot->doc);
50195309 if (outroot) xmlFreeDoc(outroot->doc);
50755365 /* Remote mailbox */
50765366 struct backend *be;
50775367
5078 be = proxy_findserver(server, &http_protocol, httpd_userid,
5368 be = proxy_findserver(server, &http_protocol, proxy_userid,
50795369 &backend_cached, NULL, NULL, httpd_in);
50805370 if (!be) return HTTP_UNAVAILABLE;
50815371
50875377 if (!*lparams->davdb.db) {
50885378 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
50895379 "Check 'configdirectory' permissions or "
5090 "'proxyservers' option on backend server.", httpd_userid);
5380 "'proxyservers' option on backend server.", proxy_userid);
50915381 txn->error.desc = "DAV database is not opened";
50925382 return HTTP_SERVER_ERROR;
50935383 }
51605450 }
51615451
51625452 /* Check any preconditions */
5163 precond = lparams->check_precond(txn, ddata, etag, lastmod, 0);
5453 precond = lparams->check_precond(txn, ddata, etag, lastmod);
51645454
51655455 if (precond != HTTP_OK) {
51665456 /* We failed a precondition - don't perform the request */
7070 #define XML_NS_ISCHED "urn:ietf:params:xml:ns:ischedule"
7171 #define XML_NS_CS "http://calendarserver.org/ns/"
7272 #define XML_NS_CYRUS "http://cyrusimap.org/ns/"
73 #define XML_NS_ICAL "http://apple.com/ns/ical/"
7473
7574 /* Index into known namespace array */
7675 enum {
8079 NS_ISCHED,
8180 NS_CS,
8281 NS_CYRUS,
83 NS_ICAL
84 };
85 #define NUM_NAMESPACE 7
82 };
83 #define NUM_NAMESPACE 6
8684
8785 /* Cyrus-specific privileges */
8886 #define DACL_MKCOL ACL_CREATE /* CY:make-collection */
153151
154152 /* Bitmask of calendar components */
155153 enum {
156 CAL_COMP_VCALENDAR = (0<<0),
154 CAL_COMP_VCALENDAR = 0xf000,
157155 CAL_COMP_VEVENT = (1<<0),
158156 CAL_COMP_VTODO = (1<<1),
159157 CAL_COMP_VJOURNAL = (1<<2),
250248
251249 /* Context for fetching properties */
252250 struct propfind_entry_list;
251 struct error_t;
253252
254253 struct propfind_ctx {
255254 struct request_target_t *req_tgt; /* parsed request target URL */
255 unsigned mode; /* none, allprop, propname, prop */
256256 unsigned depth; /* 0 = root, 1 = calendar, 2 = resrc */
257257 unsigned prefer; /* bitmask of client preferences */
258258 const char *userid; /* userid client has logged in as */
259 const char *int_userid; /* internal userid */
259260 int userisadmin; /* is userid an admin */
260261 struct auth_state *authstate; /* authorization state for userid */
261262 void *davdb; /* DAV DB corresponding to userid */
275276 void *data);
276277 struct propfind_entry_list *elist; /* List of props to fetch w/callbacks */
277278 xmlNodePtr root; /* root node to add to XML tree */
278 xmlNsPtr *ns; /* Array of our supported namespaces */
279 const char **errstr; /* Error string to pass up to caller */
279 xmlNsPtr *ns; /* Array of our known namespaces */
280 struct hash_table *ns_table; /* Table of all ns attached to resp */
281 unsigned prefix_count; /* Count of new ns added to resp */
282 struct error_t *err; /* Error info to pass up to caller */
280283 int *ret; /* Return code to pass up to caller */
281284 int fetcheddata; /* Did we fetch iCalendar/vCard data? */
282285 struct buf buf; /* Working buffer */
284287
285288 /* Function to check headers for preconditions */
286289 typedef int (*check_precond_t)(struct transaction_t *txn, const void *data,
287 const char *etag, time_t lastmod,
288 unsigned long len);
290 const char *etag, time_t lastmod);
289291
290292 /* Function to insert/update DAV resource in 'data', optionally commiting txn */
291293 typedef int (*db_write_proc_t)(void *davdb, void *data, int commit);
400402 xmlNodePtr init_xml_response(const char *resp, int ns,
401403 xmlNodePtr req, xmlNsPtr *respNs);
402404
403 struct error_t;
404405 xmlNodePtr xml_add_error(xmlNodePtr root, struct error_t *err,
405406 xmlNsPtr *avail_ns);
406407 void xml_add_lockdisc(xmlNodePtr node, const char *path, struct dav_data *data);
168168 int precond;
169169 struct message_guid guid;
170170 const char *etag;
171 unsigned long offset = 0, datalen;
172171 static time_t lastmod = 0;
173172 static xmlChar *buf = NULL;
174173 static int bufsiz = 0;
188187 etag = message_guid_encode(&guid);
189188
190189 /* Check any preconditions, including range request */
191 datalen = bufsiz;
192 txn->flags.ranges = !txn->flags.ce;
193 precond = check_precond(txn, NULL, etag, compile_time, datalen);
190 txn->flags.ranges = 1;
191 precond = check_precond(txn, NULL, etag, compile_time);
194192
195193 switch (precond) {
196194 case HTTP_OK:
197 break;
198
199195 case HTTP_PARTIAL:
200 /* Set data parameters for range */
201 offset += txn->resp_body.range->first;
202 datalen = txn->resp_body.range->last - txn->resp_body.range->first + 1;
203 break;
204
205196 case HTTP_NOT_MODIFIED:
206 /* Fill in ETag for 304 response */
197 /* Fill in Etag, Last-Modified, Expires, and iSchedule-Capabilities */
207198 txn->resp_body.etag = etag;
199 txn->resp_body.lastmod = compile_time;
200 txn->resp_body.maxage = 86400; /* 24 hrs */
201 txn->flags.cc |= CC_MAXAGE;
202 txn->resp_body.iserial = compile_time;
203
204 if (precond != HTTP_NOT_MODIFIED) break;
208205
209206 default:
210207 /* We failed a precondition - don't perform the request */
211208 return precond;
212209 }
213
214 /* Fill in Etag, Last-Modified, and iSchedule-Capabilities */
215 txn->resp_body.etag = etag;
216 txn->resp_body.lastmod = compile_time;
217 txn->resp_body.iserial = compile_time;
218210
219211 if (txn->resp_body.lastmod > lastmod) {
220212 xmlNodePtr root, capa, node, comp, meth;
277269 }
278270
279271 lastmod = txn->resp_body.lastmod;
280 datalen = bufsiz;
281272 }
282273
283274 /* Output the XML response */
284275 txn->resp_body.type = "application/xml; charset=utf-8";
285 if (txn->resp_body.range && txn->resp_body.range->next) {
286 /* multiple ranges */
287 multipart_byteranges(txn, (char *) buf);
288 }
289 else write_body(precond, txn, (char *) buf + offset, datalen);
276 write_body(precond, txn, (char *) buf, bufsiz);
290277
291278 return 0;
292279 }
834821 static int meth_get_domainkey(struct transaction_t *txn,
835822 void *params __attribute__((unused)))
836823 {
824 txn->flags.cc |= CC_REVALIDATE;
837825 txn->resp_body.type = "text/plain";
838826
839827 return meth_get_doc(txn, NULL);
635635 if (flags->conn) {
636636 /* Construct Connection header */
637637 const char *conn_tokens[] =
638 { "close", "Keep-Alive", NULL };
638 { "close", "Upgrade", "Keep-Alive", NULL };
639639
640640 if (flags->conn & CONN_KEEPALIVE) {
641641 prot_printf(httpd_out, "Keep-Alive: timeout=%d\r\n", httpd_timeout);
705705 write_forwarding_hdrs(be->out, txn->req_hdrs, txn->req_line.ver,
706706 https ? "https" : "http");
707707 spool_enum_hdrcache(txn->req_hdrs, &write_cachehdr, be->out);
708 if (spool_getheader(txn->req_hdrs, "Transfer-Encoding") ||
708 if (http_methods[txn->meth].flags & METH_NOBODY)
709 prot_puts(be->out, "Content-Length: 0\r\n");
710 else if (spool_getheader(txn->req_hdrs, "Transfer-Encoding") ||
709711 spool_getheader(txn->req_hdrs, "Content-Length")) {
710712 prot_puts(be->out, "Expect: 100-continue\r\n");
711713 prot_puts(be->out, "Transfer-Encoding: chunked\r\n");
5151 #include <assert.h>
5252
5353 #include "acl.h"
54 #include "annotate.h"
5455 #include "charset.h"
5556 #include "global.h"
5657 #include "httpd.h"
7273 #include "xmalloc.h"
7374 #include "xstrlcpy.h"
7475
75 #define RSS_SPEC "http://www.rssboard.org/rss-specification"
7676 #define XML_NS_ATOM "http://www.w3.org/2005/Atom"
77 #define XML_NS_CYRUS "http://cyrusimap.org/ns/"
7778 #define MAX_SECTION_LEN 128
7879 #define FEEDLIST_VAR "%RSS_FEEDLIST%"
7980
200201 /* Remote mailbox */
201202 struct backend *be;
202203
203 be = proxy_findserver(server, &http_protocol, httpd_userid,
204 be = proxy_findserver(server, &http_protocol, proxy_userid,
204205 &backend_cached, NULL, NULL, httpd_in);
205206 if (!be) return HTTP_UNAVAILABLE;
206207
256257 const char *etag = NULL;
257258 time_t lastmod = 0;
258259 struct resp_body_t *resp_body = &txn->resp_body;
259 unsigned long offset = 0, datalen = 0;
260260
261261 /* Check any preconditions */
262262 if (!strcmp(section, "0")) {
263263 /* Entire raw message */
264 txn->flags.ranges = !txn->flags.ce;
265 datalen = msg_size;
264 txn->flags.ranges = 1;
266265 }
267266
268267 etag = message_guid_encode(&record.guid);
269268 lastmod = record.internaldate;
270 precond = check_precond(txn, NULL, etag, lastmod, datalen);
269 precond = check_precond(txn, NULL, etag, lastmod);
271270
272271 switch (precond) {
273272 case HTTP_OK:
274 break;
275
276273 case HTTP_PARTIAL:
277 /* Set data parameters for range */
278 offset += resp_body->range->first;
279 datalen = resp_body->range->last - resp_body->range->first + 1;
280 break;
281
282274 case HTTP_NOT_MODIFIED:
283 /* Fill in ETag for 304 response */
275 /* Fill in ETag, Last-Modified, and Expires */
284276 resp_body->etag = etag;
277 resp_body->lastmod = lastmod;
278 resp_body->maxage = 31536000; /* 1 year */
279 txn->flags.cc |= CC_MAXAGE;
280
281 if (precond != HTTP_NOT_MODIFIED) break;
285282
286283 default:
287284 /* We failed a precondition - don't perform the request */
289286 goto done;
290287 }
291288
292 /* Fill in ETag and Last-Modified */
293 resp_body->etag = etag;
294 resp_body->lastmod = lastmod;
295
296289 if (!*section) {
297290 /* Return entire message formatted as text/html */
298291 display_message(txn, mailbox->name, record.uid, body, msg_base);
300293 else if (!strcmp(section, "0")) {
301294 /* Return entire message as text/plain */
302295 resp_body->type = "text/plain";
303
304 if (resp_body->range && resp_body->range->next) {
305 /* multiple ranges */
306 multipart_byteranges(txn, msg_base);
307 }
308 else write_body(precond, txn, msg_base + offset, datalen);
296 write_body(precond, txn, msg_base, msg_size);
309297 }
310298 else {
311299 /* Fetch, decode, and return the specified MIME message part */
424412 /* Found closest ancestor of 'name' */
425413 struct node *node;
426414 size_t len = matchlen;
427 char *cp, *shortname, path[MAX_MAILBOX_PATH+1], *href = NULL;
415 char shortname[MAX_MAILBOX_NAME+1], path[MAX_MAILBOX_PATH+1];
416 char *cp, *href = NULL;
428417
429418 /* Send a body chunk once in a while */
430419 if (buf_len(buf) > PROT_BUFSIZE) {
457446 lrock->last = last->child = node;
458447
459448 /* Get last segment of mailbox name */
460 if ((shortname = strrchr(node->name, '.'))) shortname++;
461 else shortname = node->name;
449 if ((cp = strrchr(node->name, '.'))) cp++;
450 else cp = node->name;
451
452 /* Translate short mailbox name to external form */
453 strlcpy(shortname, cp, sizeof(shortname));
454 mboxname_hiersep_toexternal(&httpd_namespace, shortname, 0);
462455
463456 if (href) {
464457 /* Add selectable feed with link */
564557 buf_printf(&txn->buf, "-%ld-%ld", sbuf.st_mtime, sbuf.st_size);
565558
566559 /* Check any preconditions */
567 precond = check_precond(txn, NULL, buf_cstring(&txn->buf), lastmod, 0);
560 precond = check_precond(txn, NULL, buf_cstring(&txn->buf), lastmod);
568561
569562 switch (precond) {
570563 case HTTP_OK:
571 break;
572
573564 case HTTP_NOT_MODIFIED:
574 /* Fill in ETag for 304 response */
565 /* Fill in ETag, Last-Modified, and Expires */
575566 txn->resp_body.etag = buf_cstring(&txn->buf);
567 txn->resp_body.lastmod = lastmod;
568 txn->resp_body.maxage = 86400; /* 24 hrs */
569 txn->flags.cc |= CC_MAXAGE;
570
571 if (precond != HTTP_NOT_MODIFIED) break;
576572
577573 default:
578574 /* We failed a precondition - don't perform the request */
580576 goto done;
581577 }
582578
583 /* Fill in ETag, Last-Modified, and Content-Type */
584 txn->resp_body.etag = buf_cstring(&txn->buf);
585 txn->resp_body.lastmod = lastmod;
586 txn->resp_body.type = "text/html; charset=utf-8";
587
588579 /* Setup for chunked response */
589580 txn->flags.te |= TE_CHUNKED;
581 txn->resp_body.type = "text/html; charset=utf-8";
590582
591583 /* Short-circuit for HEAD request */
592584 if (txn->meth == METH_HEAD) {
641633 if ((r == CYRUSDB_NOTFOUND) ||
642634 (record->system_flags & (FLAG_DELETED|FLAG_EXPUNGED))) {
643635 txn->error.desc = "Message has been removed\r\n";
636
637 /* Fill in Expires */
638 txn->resp_body.maxage = 31536000; /* 1 year */
639 txn->flags.cc |= CC_MAXAGE;
644640 return HTTP_GONE;
645641 }
646642 else if (r) {
672668 const char *c;
673669 unsigned buflen = buf_len(buf), len = 0;
674670
671 if (!replace && config_httpprettytelemetry)
672 buf_printf(buf, "%*s", level * MARKUP_INDENT, "");
673
675674 for (c = str; c && *c && (!max || len < max); c++, len++) {
676675 /* Translate CR to HTML <br> tag */
677676 if (*c == '\r') buf_appendcstr(buf, "<br>");
679678
680679 /* Translate XML/HTML specials */
681680 else if (*c == '"') buf_appendcstr(buf, "&quot;");
682 else if (*c == '\'') buf_appendcstr(buf, "&apos;");
681 // else if (*c == '\'') buf_appendcstr(buf, "&apos;");
683682 else if (*c == '&') buf_appendcstr(buf, "&amp;");
684683 else if (*c == '<') buf_appendcstr(buf, "&lt;");
685684 else if (*c == '>') buf_appendcstr(buf, "&gt;");
685
686 /* Handle multi-byte UTF-8 sequences */
687 else if ((*c & 0xc0) == 0xc0) {
688 /* Code points larger than 127 are represented by
689 * multi-byte sequences, composed of a leading byte and
690 * one or more continuation bytes. The leading byte has
691 * two or more high-order 1s followed by a 0, while
692 * continuation bytes all have '10' in the high-order
693 * position. The number of high-order 1s in the leading
694 * byte of a multi-byte sequence indicates the number of
695 * bytes in the sequence.
696 */
697 unsigned char lead = *c;
698
699 do buf_putc(buf, *c);
700 while (((lead <<= 1) & 0x80) && c++);
701 }
686702
687703 /* Check for non-printable chars */
688704 else if (!(isspace(*c) || isprint(*c))) {
704720
705721 else buf_putc(buf, *c);
706722 }
723
724 if (!replace && config_httpprettytelemetry) buf_appendcstr(buf, "\n");
725 }
726
727
728 /* Create RFC3339 date ('buf' must be at least 21 characters) */
729 static void rfc3339date_gen(char *buf, size_t len, time_t t)
730 {
731 struct tm *tm = gmtime(&t);
732
733 snprintf(buf, len, "%4d-%02d-%02dT%02d:%02d:%02dZ",
734 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
735 tm->tm_hour, tm->tm_min, tm->tm_sec);
707736 }
708737
709738
710739 /* List messages as an RSS feed */
711740 static int list_messages(struct transaction_t *txn, struct mailbox *mailbox)
712741 {
713 const char **fwd, *proto = NULL, *host = NULL, *webmaster;
742 const char **fwd, *proto = NULL, *host = NULL;
714743 uint32_t url_len, recno, recentuid = 0;
715 int max_age, max_items, max_len, ttl, nitems, precond;
744 int max_age, max_items, max_len, nitems, precond;
716745 time_t age_mark = 0, lastmod;
717746 char datestr[80];
718747 static char etag[33];
719748 struct buf *url = &txn->buf;
720749 struct buf *buf = &txn->resp_body.payload;
721750 unsigned level = 0;
751 char mboxname[MAX_MAILBOX_NAME+1];
752 struct annotation_data attrib;
722753
723754 /* Check any preconditions */
724755 lastmod = mailbox->i.last_appenddate;
725756 sprintf(etag, "%u-%u-%u",
726757 mailbox->i.uidvalidity, mailbox->i.last_uid, mailbox->i.exists);
727 precond = check_precond(txn, NULL, etag, lastmod, 0);
758 precond = check_precond(txn, NULL, etag, lastmod);
728759
729760 switch (precond) {
730761 case HTTP_OK:
731 break;
732
733762 case HTTP_NOT_MODIFIED:
734 /* Fill in ETag for 304 response */
763 /* Fill in ETag, Last-Modified, and Expires */
735764 txn->resp_body.etag = etag;
765 txn->resp_body.lastmod = lastmod;
766 txn->resp_body.maxage = 3600; /* 1 hr */
767 txn->flags.cc |= CC_MAXAGE;
768
769 if (precond != HTTP_NOT_MODIFIED) break;
736770
737771 default:
738772 /* We failed a precondition - don't perform the request */
739773 return precond;
740774 }
741775
742 /* Fill in ETag and Last-Modified */
743 txn->resp_body.etag = etag;
744 txn->resp_body.lastmod = lastmod;
745
746776 /* Setup for chunked response */
747777 txn->flags.te |= TE_CHUNKED;
748 txn->resp_body.type = "application/xml; charset=utf-8";
778 txn->resp_body.type = "application/atom+xml; charset=utf-8";
749779
750780 /* Short-circuit for HEAD request */
751781 if (txn->meth == METH_HEAD) {
764794 /* Get length of description to display */
765795 max_len = config_getint(IMAPOPT_RSS_MAXSYNOPSIS);
766796 if (max_len < 0) max_len = 0;
767
768 /* Get TTL */
769 ttl = config_getint(IMAPOPT_RSS_TTL);
770 if (ttl < 0) ttl = 0;
771797
772798 #if 0
773799 /* Obtain recentuid */
798824 }
799825 #endif
800826
801 /* Start XML */
802 buf_reset(buf);
803 buf_printf_markup(buf, level, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
804
805 /* Set up the RSS <channel> response for the mailbox */
806 buf_printf_markup(buf, level++,
807 "<rss xmlns:atom=\"" XML_NS_ATOM "\" version=\"2.0\">");
808 buf_printf_markup(buf, level++, "<channel>");
809 buf_printf_markup(buf, level, "<title>%s</title>", mailbox->name);
827 /* Translate mailbox name to external form */
828 strlcpy(mboxname, mailbox->name, sizeof(mboxname));
829 mboxname_hiersep_toexternal(&httpd_namespace, mboxname, 0);
810830
811831 /* Construct base URL */
812832 if (config_mupdate_server && config_getstring(IMAPOPT_PROXYSERVERS) &&
813833 (fwd = spool_getheader(txn->req_hdrs, "Forwarded"))) {
814834 /* Proxied request - parse last Forwarded header for proto and host */
815835 /* XXX This is destructive of the header but we don't care
816 * and more importantly, need the tokens available after tok_fini()
836 * and more importantly, we need the tokens available after tok_fini()
817837 */
818838 tok_t tok;
819839 char *token;
836856 buf_printf(url, "%s://%s%s", proto, host, txn->req_uri->path);
837857 url_len = buf_len(url);
838858
839 buf_printf_markup(buf, level, "<link>%s</link>", buf_cstring(url));
840
841 /* Recommended by http://www.rssboard.org/rss-profile */
842 buf_printf_markup(buf, level, "<atom:link href=\"%s\" rel=\"self\""
843 " type=\"application/rss+xml\"/>", buf_cstring(url));
844
845 /* XXX Use /comment annotation as description? */
846 buf_printf_markup(buf, level, "<description/>");
847
848 if ((webmaster = config_getstring(IMAPOPT_RSS_WEBMASTER))) {
849 buf_printf_markup(buf, level, "<webMaster>%s</webMaster>", webmaster);
850 }
851
852 rfc822date_gen(datestr, sizeof(datestr), time(NULL));
853 buf_printf_markup(buf, level, "<pubDate>%s</pubDate>", datestr);
854
855 rfc822date_gen(datestr, sizeof(datestr), lastmod);
856 buf_printf_markup(buf, level, "<lastBuildDate>%s</lastBuildDate>", datestr);
857
859 /* Start XML */
860 buf_reset(buf);
861 buf_printf_markup(buf, level, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
862
863 /* Set up the Atom <feed> response for the mailbox */
864 buf_printf_markup(buf, level++,
865 "<feed xmlns=\"" XML_NS_ATOM "\">");
866
867 /* <title> - required */
868 buf_printf_markup(buf, level, "<title>%s</title>", mboxname);
869
870 /* <id> - required */
871 buf_printf_markup(buf, level, "<id>%sguid/%s</id>",
872 XML_NS_CYRUS, mailbox->uniqueid);
873
874 /* <updated> - required */
875 rfc3339date_gen(datestr, sizeof(datestr), lastmod);
876 buf_printf_markup(buf, level, "<updated>%s</updated>", datestr);
877
878 /* <author> - required (use 'Anonymous' as default <name>) */
879 buf_printf_markup(buf, level++, "<author>");
880 buf_printf_markup(buf, level, "<name>Anonymous</name>");
881 buf_printf_markup(buf, --level, "</author>");
882
883 /* <subtitle> - optional */
884 memset(&attrib, 0, sizeof(struct annotation_data));
885 annotatemore_lookup(mailbox->name, "/comment", "", &attrib);
886 if (age_mark) {
887 rfc822date_gen(datestr, sizeof(datestr), age_mark);
888 buf_printf_markup(buf, level,
889 "<subtitle>%s [posts since %s]</subtitle>",
890 attrib.value ? attrib.value : "", datestr);
891 }
892 else {
893 buf_printf_markup(buf, level,
894 "<subtitle>%s [%u most recent posts]</subtitle>",
895 attrib.value ? attrib.value : "",
896 max_items ? (unsigned) max_items : mailbox->i.exists);
897 }
898
899 /* <link> - optional */
900 buf_printf_markup(buf, level,
901 "<link rel=\"self\" type=\"application/atom+xml\""
902 " href=\"%s\"/>", buf_cstring(url));
903
904 /* <generator> - optional */
858905 if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) {
859906 buf_printf_markup(buf, level, "<generator>Cyrus HTTP %s</generator>",
860907 cyrus_version());
861908 }
862909
863 buf_printf_markup(buf, level, "<docs>%s</docs>", RSS_SPEC);
864
865 if (ttl) buf_printf_markup(buf, level, "<ttl>%d</ttl>", ttl);
866
867910 write_body(HTTP_OK, txn, buf_cstring(buf), buf_len(buf));
868911 buf_reset(buf);
869912
870 /* Add an <item> for each message */
913 /* Add an <entry> for each message */
871914 for (recno = mailbox->i.num_records, nitems = 0;
872915 recno >= 1 && (!max_items || nitems < max_items); recno--) {
873916 struct index_record record;
875918 unsigned long msg_size;
876919 struct body *body;
877920 char *subj;
921 struct address *addr = NULL;
878922 const char *content_types[] = { "text", NULL };
879923 struct message_content content;
880924 struct bodypart **parts;
904948 /* Feeding this message, increment counter */
905949 nitems++;
906950
907 buf_printf_markup(buf, level++, "<item>");
908
951 buf_printf_markup(buf, level++, "<entry>");
952
953 /* <title> - required */
909954 subj = charset_parse_mimeheader(body->subject);
910 buf_printf_markup(buf, level++, "<title>");
911 if (config_httpprettytelemetry)
912 buf_printf(buf, "%*s", level * MARKUP_INDENT, "");
913 buf_escapestr(buf, subj && *subj ? subj : "[Untitled]", 0, 0, 0);
914 if (config_httpprettytelemetry) buf_appendcstr(buf, "\n");
955 buf_printf_markup(buf, level++, "<title type=\"html\">");
956 buf_escapestr(buf, subj && *subj ? subj : "[Untitled]", 0, 0, level);
915957 buf_printf_markup(buf, --level, "</title>");
916958 free(subj);
917959
960 /* <id> - required */
961 buf_printf_markup(buf, level, "<id>%sguid/%s</id>",
962 XML_NS_CYRUS, message_guid_encode(&record.guid));
963
964 /* <updated> - required */
965 rfc3339date_gen(datestr, sizeof(datestr), record.gmtime);
966 buf_printf_markup(buf, level, "<updated>%s</updated>", datestr);
967
968 /* <published> - optional */
969 buf_printf_markup(buf, level, "<published>%s</published>", datestr);
970
971 /* <link> - optional */
918972 buf_truncate(url, url_len);
919973 buf_printf(url, "?uid=%u", record.uid);
920 buf_printf_markup(buf, level, "<link>%s</link>", buf_cstring(url));
921
922 if (body->from || body->sender) {
923 struct address *addr;
924
925 if (body->from) addr = body->from;
926 else addr = body->sender;
927
928 if (*addr->mailbox) {
929 buf_printf_markup(buf, level++, "<author>");
930 if (config_httpprettytelemetry)
931 buf_printf(buf, "%*s", level * MARKUP_INDENT, "");
932 buf_escapestr(buf, addr->mailbox, 0, 0, 0);
933 buf_putc(buf, '@');
934 buf_escapestr(buf, addr->domain, 0, 0, 0);
935 if (addr->name) {
936 buf_appendcstr(buf, " (");
937 buf_escapestr(buf, addr->name, 0, 0, 0);
938 buf_putc(buf, ')');
939 }
940 if (config_httpprettytelemetry) buf_appendcstr(buf, "\n");
941 buf_printf_markup(buf, --level, "</author>");
942 }
943 }
944
945 buf_printf_markup(buf, level, "<guid isPermaLink=\"false\">%s</guid>",
946 message_guid_encode(&record.guid));
947
948 rfc822date_gen(datestr, sizeof(datestr), record.gmtime);
949 buf_printf_markup(buf, level, "<pubDate>%s</pubDate>", datestr);
950
951 /* Find and use the first text/ part as the <description> */
974 buf_printf_markup(buf, level, "<link rel=\"alternate\""
975 " type=\"text/html\" href=\"%s\"/>",
976 buf_cstring(url));
977
978 /* <author> - optional */
979 addr = body->from;
980 if (!addr) addr = body->sender;
981 if (addr && *addr->mailbox) {
982 buf_printf_markup(buf, level++, "<author>");
983
984 /* <name> - required */
985 if (addr->name) {
986 char *name = charset_parse_mimeheader(addr->name);
987 buf_printf_markup(buf, level++, "<name>");
988 buf_escapestr(buf, name, 0, 0, level);
989 buf_printf_markup(buf, --level, "</name>");
990 free(name);
991 }
992 else {
993 buf_printf_markup(buf, level, "<name>%s@%s</name>",
994 addr->mailbox, addr->domain);
995 }
996
997 /* <email> - optional */
998 buf_printf_markup(buf, level, "<email>%s@%s</email>",
999 addr->mailbox, addr->domain);
1000
1001 buf_printf_markup(buf, --level, "</author>");
1002 }
1003
1004 /* <summary> - optional (find and use the first text/ part) */
9521005 content.base = msg_base;
9531006 content.len = msg_size;
9541007 content.body = body;
9551008 message_fetch_part(&content, content_types, &parts);
9561009
9571010 if (parts && *parts) {
958 buf_printf_markup(buf, level++, "<description>");
1011 buf_printf_markup(buf, level++, "<summary type=\"html\">");
9591012 buf_printf_markup(buf, level++, "<![CDATA[");
9601013 buf_escapestr(buf, parts[0]->decoded_body, max_len, 1, level);
9611014 buf_printf_markup(buf, --level, "]]>");
962 buf_printf_markup(buf, --level, "</description>");
963 }
964
965 buf_printf_markup(buf, --level, "</item>");
1015 buf_printf_markup(buf, --level, "</summary>");
1016 }
1017
1018 buf_printf_markup(buf, --level, "</entry>");
9661019
9671020 /* free the results */
9681021 if (parts) {
9801033 }
9811034 }
9821035
983 /* End of RSS <channel> */
984 buf_printf_markup(buf, --level, "</channel>");
985 buf_printf_markup(buf, --level, "</rss>");
1036 /* End of Atom <feed> */
1037 buf_printf_markup(buf, --level, "</feed>");
9861038 write_body(0, txn, buf_cstring(buf), buf_len(buf));
9871039
9881040 /* End of output */
126126 static struct mailbox *httpd_mailbox = NULL;
127127 static struct wildmat *allow_cors = NULL;
128128 int httpd_timeout, httpd_keepalive;
129 char *httpd_userid = 0;
129 char *httpd_userid = NULL, *proxy_userid = NULL;
130130 struct auth_state *httpd_authstate = 0;
131131 int httpd_userisadmin = 0;
132132 int httpd_userisproxyadmin = 0;
204204 static int parse_expect(struct transaction_t *txn);
205205 static void parse_connection(struct transaction_t *txn);
206206 static struct accept *parse_accept(const char **hdr);
207 static int parse_ranges(const char *hdr, unsigned long len,
208 struct range **ranges);
207209 static int http_auth(const char *creds, struct transaction_t *txn);
208210 static void keep_alive(int sig);
209211
228230 char *token;
229231 float qual;
230232 struct accept *next;
231 };
232
233 /* Flags for known methods*/
234 enum {
235 METH_NOBODY = (1<<0), /* Method does not expect a body */
236233 };
237234
238235 /* Array of HTTP methods known by our server. */
369366 free(httpd_userid);
370367 httpd_userid = NULL;
371368 }
369 if (proxy_userid != NULL) {
370 free(proxy_userid);
371 proxy_userid = NULL;
372 }
372373 if (httpd_authstate) {
373374 auth_freestate(httpd_authstate);
374375 httpd_authstate = NULL;
927928 txn.meth = METH_UNKNOWN;
928929 memset(&txn.flags, 0, sizeof(struct txn_flags_t));
929930 txn.flags.conn = 0;
930 txn.flags.vary = gzip_enabled ? VARY_AE : 0;
931 txn.flags.vary = VARY_AE;
931932 memset(req_line, 0, sizeof(struct request_line_t));
932933 memset(&txn.req_tgt, 0, sizeof(struct request_target_t));
933934 txn.req_uri = NULL;
13311332 for (e = enc; e && e->token; e++) {
13321333 if (!strcasecmp(e->token, "gzip") ||
13331334 !strcasecmp(e->token, "x-gzip")) {
1334 txn.flags.ce = CE_GZIP;
1335 txn.resp_body.enc = CE_GZIP;
13351336 }
13361337 free(e->token);
13371338 }
13601361 buf_free(&txn.resp_body.payload);
13611362 #ifdef HAVE_ZLIB
13621363 deflateEnd(&txn.zstrm);
1364 buf_free(&txn.zbuf);
13631365 #endif
13641366 return;
13651367 }
18531855 #define Access_Control_Expose(hdr) \
18541856 prot_puts(httpd_out, "Access-Control-Expose-Headers: " hdr "\r\n")
18551857
1856 void comma_list_hdr(const char *hdr, const char *vals[], unsigned flags)
1857 {
1858 const char *sep = "";
1858 void comma_list_hdr(const char *hdr, const char *vals[], unsigned flags, ...)
1859 {
1860 const char *sep = " ";
1861 va_list args;
18591862 int i;
18601863
1864 va_start(args, flags);
18611865 prot_printf(httpd_out, "%s:", hdr);
18621866 for (i = 0; vals[i]; i++) {
18631867 if (flags & (1 << i)) {
1864 prot_printf(httpd_out, "%s %s", sep, vals[i]);
1865 sep = ",";
1868 prot_puts(httpd_out, sep);
1869 prot_vprintf(httpd_out, vals[i], args);
1870 sep = ", ";
1871 }
1872 else {
1873 /* discard any unused args */
1874 vsnprintf(NULL, 0, vals[i], args);
18661875 }
18671876 }
18681877 prot_puts(httpd_out, "\r\n");
1878 va_end(args);
18691879 }
18701880
18711881 void allow_hdr(const char *hdr, unsigned allow)
19821992 if (txn->flags.cc) {
19831993 /* Construct Cache-Control header */
19841994 const char *cc_dirs[] =
1985 { "no-cache", "no-transform", "private", NULL };
1986
1987 comma_list_hdr("Cache-Control", cc_dirs, txn->flags.cc);
1995 { "must-revalidate", "no-cache", "no-store", "no-transform",
1996 "public", "private", "max-age=%d", NULL };
1997
1998 comma_list_hdr("Cache-Control", cc_dirs, txn->flags.cc,
1999 resp_body->maxage);
2000
2001 if (txn->flags.cc & CC_MAXAGE) {
2002 httpdate_gen(datestr, sizeof(datestr), now + resp_body->maxage);
2003 prot_printf(httpd_out, "Expires: %s\r\n", datestr);
2004 }
19882005 }
19892006 if (txn->flags.cors) {
19902007 /* Construct Cross-Origin Resource Sharing headers */
21322149 if (txn->flags.cors) Access_Control_Expose("Schedule-Tag");
21332150 }
21342151 if (resp_body->etag) {
2135 prot_printf(httpd_out, "ETag: \"%s\"\r\n", resp_body->etag);
2152 prot_printf(httpd_out, "ETag: %s\"%s\"\r\n",
2153 resp_body->enc ? "W/" : "", resp_body->etag);
21362154 if (txn->flags.cors) Access_Control_Expose("ETag");
21372155 }
21382156 if (resp_body->lastmod) {
21472165 if (resp_body->type) {
21482166 prot_printf(httpd_out, "Content-Type: %s\r\n", resp_body->type);
21492167
2150 if (resp_body->enc) {
2151 prot_printf(httpd_out, "Content-Encoding: %s\r\n", resp_body->enc);
2168 if (txn->resp_body.enc) {
2169 /* Construct Content-Encoding header */
2170 const char *ce[] =
2171 { "deflate", "gzip", NULL };
2172
2173 comma_list_hdr("Content-Encoding", ce, txn->resp_body.enc);
21522174 }
21532175 if (resp_body->lang) {
21542176 prot_printf(httpd_out, "Content-Language: %s\r\n", resp_body->lang);
21672189 /* MUST NOT include a body */
21682190 break;
21692191
2192 case HTTP_UNSAT_RANGE:
2193 prot_printf(httpd_out, "Content-Range: bytes */%lu\r\n",
2194 resp_body->len);
2195 resp_body->len = 0; /* No content */
2196
2197 /* Fall through and specify framing */
2198
21702199 case HTTP_PARTIAL:
2171 case HTTP_UNSAT_RANGE:
21722200 if (resp_body->range) {
2173 prot_puts(httpd_out, "Content-Range: bytes ");
2174 if (code == HTTP_PARTIAL) {
2175 prot_printf(httpd_out, "%lu-%lu",
2176 resp_body->range->first, resp_body->range->last);
2177 }
2178 else prot_printf(httpd_out, "*");
2179 prot_printf(httpd_out, "/%lu\r\n", resp_body->range->len);
2201 prot_printf(httpd_out, "Content-Range: bytes %lu-%lu/%lu\r\n",
2202 resp_body->range->first, resp_body->range->last,
2203 resp_body->len);
2204
2205 /* Set actual content length of range */
2206 resp_body->len = resp_body->range->last -
2207 resp_body->range->first + 1;
21802208
21812209 free(resp_body->range);
21822210 }
21872215 if (txn->flags.te) {
21882216 /* HTTP/1.1+ only - we use close-delimiting for HTTP/1.0 */
21892217 if (!txn->flags.ver1_0) {
2190 prot_puts(httpd_out, "Transfer-Encoding:");
2191 if (txn->flags.te & TE_GZIP)
2192 prot_puts(httpd_out, " gzip,");
2193 else if (txn->flags.te & TE_DEFLATE)
2194 prot_puts(httpd_out, " deflate,");
2195
2196 /* Any TE implies "chunked", which is always last */
2197 prot_puts(httpd_out, " chunked\r\n");
2218 /* Construct Transfer-Encoding header */
2219 const char *te[] =
2220 { "deflate", "gzip", "chunked", NULL };
2221
2222 comma_list_hdr("Transfer-Encoding", te, txn->flags.te);
21982223 }
21992224 }
22002225 else prot_printf(httpd_out, "Content-Length: %lu\r\n", resp_body->len);
22092234 buf_reset(&log);
22102235 /* Add client data */
22112236 buf_printf(&log, "%s", httpd_clienthost);
2212 if (httpd_userid) buf_printf(&log, " as \"%s\"", httpd_userid);
2237 if (proxy_userid) buf_printf(&log, " as \"%s\"", proxy_userid);
22132238 if (txn->req_hdrs &&
22142239 (hdr = spool_getheader(txn->req_hdrs, "User-Agent"))) {
22152240 buf_printf(&log, " with \"%s\"", hdr[0]);
22782303 static void keep_alive(int sig)
22792304 {
22802305 if (sig == SIGALRM) response_header(HTTP_PROCESSING, NULL);
2281 }
2282
2283
2284 /* List of incompressible MIME types */
2285 static const char *comp_mime[] = {
2286 "image/gif",
2287 "image/jpeg",
2288 "image/png",
2289 NULL
2290 };
2291
2292
2293 /* Determine if a MIME type is incompressible */
2294 static int is_incompressible(const char *type)
2295 {
2296 const char **m;
2297
2298 for (m = comp_mime; *m && strcasecmp(*m, type); m++);
2299 return (*m != NULL);
23002306 }
23012307
23022308
23142320 void write_body(long code, struct transaction_t *txn,
23152321 const char *buf, unsigned len)
23162322 {
2317 #define GZIP_MIN_LEN 300
2318
2319 static unsigned is_dynamic;
2323 unsigned is_dynamic = code ? (txn->flags.te & TE_CHUNKED) : 1;
2324 unsigned outlen = len, offset = 0;
2325
2326 if (!is_dynamic && len < GZIP_MIN_LEN) {
2327 /* Don't compress small static content */
2328 txn->resp_body.enc = CE_IDENTITY;
2329 txn->flags.te = TE_NONE;
2330 }
2331
2332 /* Compress data */
2333 if (txn->resp_body.enc || txn->flags.te & ~TE_CHUNKED) {
2334 #ifdef HAVE_ZLIB
2335 /* Only flush for static content or on last (zero-length) chunk */
2336 unsigned flush = (is_dynamic && len) ? Z_NO_FLUSH : Z_FINISH;
2337
2338 if (code) deflateReset(&txn->zstrm);
2339
2340 txn->zstrm.next_in = (Bytef *) buf;
2341 txn->zstrm.avail_in = len;
2342 buf_reset(&txn->zbuf);
2343
2344 do {
2345 buf_ensure(&txn->zbuf,
2346 deflateBound(&txn->zstrm, txn->zstrm.avail_in));
2347
2348 txn->zstrm.next_out = (Bytef *) txn->zbuf.s + txn->zbuf.len;
2349 txn->zstrm.avail_out = txn->zbuf.alloc - txn->zbuf.len;
2350
2351 deflate(&txn->zstrm, flush);
2352 txn->zbuf.len = txn->zbuf.alloc - txn->zstrm.avail_out;
2353
2354 } while (!txn->zstrm.avail_out);
2355
2356 buf = txn->zbuf.s;
2357 outlen = txn->zbuf.len;
2358 #else
2359 /* XXX should never get here */
2360 fatal("Compression requested, but no zlib", EC_SOFTWARE);
2361 #endif /* HAVE_ZLIB */
2362 }
23202363
23212364 if (code) {
23222365 /* Initial call - prepare response header based on CE, TE and version */
2323 is_dynamic = (txn->flags.te & TE_CHUNKED);
2324
2325 if ((!is_dynamic && len < GZIP_MIN_LEN) ||
2326 is_incompressible(txn->resp_body.type)) {
2327 /* Don't compress small or imcompressible bodies */
2328 txn->flags.ce = CE_IDENTITY;
2329 txn->flags.te &= TE_CHUNKED;
2330 }
23312366
23322367 if (txn->flags.te & ~TE_CHUNKED) {
2333 /* compressed output will be always chunked (streamed) */
2368 /* Transfer-Encoded content MUST be chunked */
23342369 txn->flags.te |= TE_CHUNKED;
2335 }
2336 else if (txn->flags.ce) {
2337 /* set content-encoding */
2338 if (txn->flags.ce == CE_GZIP)
2339 txn->resp_body.enc = "gzip";
2340 else if (txn->flags.ce == CE_DEFLATE)
2341 txn->resp_body.enc = "deflate";
2342
2343 /* compressed output will be always chunked (streamed) */
2344 txn->flags.te |= TE_CHUNKED;
2345 }
2346 else if (!is_dynamic) {
2347 /* full body (no encoding) */
2348 txn->resp_body.len = len;
2349 }
2350
2351 if (txn->flags.ver1_0 && (txn->flags.te & TE_CHUNKED)) {
2352 /* HTTP/1.0 close-delimited data */
2370
2371 if (!is_dynamic) {
2372 /* Handle static content as last chunk */
2373 len = 0;
2374 }
2375 }
2376
2377 if (!(txn->flags.te & TE_CHUNKED)) {
2378 /* Full/partial body (no encoding).
2379 *
2380 * In all cases, 'resp_body.len' is used to specify complete-length
2381 * In the case of a 206 or 416 response, Content-Length will be
2382 * set accordingly in response_header().
2383 */
2384 txn->resp_body.len = outlen;
2385
2386 if (code == HTTP_PARTIAL) {
2387 /* check_precond() tells us that this is a range request */
2388 code = parse_ranges(*spool_getheader(txn->req_hdrs, "Range"),
2389 outlen, &txn->resp_body.range);
2390
2391 switch (code) {
2392 case HTTP_OK:
2393 /* Full body (unknown range-unit) */
2394 break;
2395
2396 case HTTP_PARTIAL:
2397 /* One or more range request(s) */
2398 txn->resp_body.len = outlen;
2399
2400 if (txn->resp_body.range->next) {
2401 /* Multiple ranges */
2402 multipart_byteranges(txn, buf);
2403 return;
2404 }
2405 else {
2406 /* Single range - set data parameters accordingly */
2407 offset += txn->resp_body.range->first;
2408 outlen = txn->resp_body.range->last -
2409 txn->resp_body.range->first + 1;
2410 }
2411 break;
2412
2413 case HTTP_UNSAT_RANGE:
2414 /* No valid ranges */
2415 outlen = 0;
2416 break;
2417 }
2418 }
2419 }
2420 else if (txn->flags.ver1_0) {
2421 /* HTTP/1.0 doesn't support chunked - close-delimit the body */
23532422 txn->flags.conn = CONN_CLOSE;
23542423 }
23552424
23692438 }
23702439 }
23712440
2372 /* Send [partial] body based on CE and TE */
2373 if (txn->flags.ce || txn->flags.te & ~TE_CHUNKED) {
2374 #ifdef HAVE_ZLIB
2375 char zbuf[PROT_BUFSIZE];
2376 unsigned flush, out;
2377
2378 if (code) deflateReset(&txn->zstrm);
2379
2380 /* don't flush until last (zero-length) or only chunk */
2381 flush = (is_dynamic && len) ? Z_NO_FLUSH : Z_FINISH;
2382
2383 txn->zstrm.next_in = (Bytef *) buf;
2384 txn->zstrm.avail_in = len;
2385
2386 do {
2387 txn->zstrm.next_out = (Bytef *) zbuf;
2388 txn->zstrm.avail_out = PROT_BUFSIZE;
2389
2390 deflate(&txn->zstrm, flush);
2391 out = PROT_BUFSIZE - txn->zstrm.avail_out;
2392
2393 if (out && !txn->flags.ver1_0) {
2394 /* HTTP/1.1 chunk of compressed output */
2395 prot_printf(httpd_out, "%x\r\n", out);
2396 prot_write(httpd_out, zbuf, out);
2397 prot_puts(httpd_out, "\r\n");
2398 }
2399 else {
2400 /* HTTP/1.0 close-delimited data */
2401 prot_write(httpd_out, zbuf, out);
2402 }
2403
2404 } while (!txn->zstrm.avail_out);
2405
2406 if (flush == Z_FINISH && !txn->flags.ver1_0) {
2407 /* terminate the HTTP/1.1 body with a zero-length chunk */
2441 /* Output data */
2442 if ((txn->flags.te & TE_CHUNKED) && !txn->flags.ver1_0) {
2443 /* HTTP/1.1 chunk */
2444 if (outlen) {
2445 prot_printf(httpd_out, "%x\r\n", outlen);
2446 prot_write(httpd_out, buf, outlen);
2447 prot_puts(httpd_out, "\r\n");
2448 }
2449 if (!len) {
2450 /* Terminate the HTTP/1.1 body with a zero-length chunk */
24082451 prot_puts(httpd_out, "0\r\n");
2409 /* empty trailer */
2452
2453 /* Empty trailer */
24102454 prot_puts(httpd_out, "\r\n");
24112455 }
2412 #else
2413 /* XXX should never get here */
2414 fatal("Content-Encoding requested, but no zlib", EC_SOFTWARE);
2415 #endif /* HAVE_ZLIB */
2416 }
2417 else if (is_dynamic && !txn->flags.ver1_0) {
2418 /* HTTP/1.1 chunk */
2419 prot_printf(httpd_out, "%x\r\n", len);
2420 if (len) prot_write(httpd_out, buf, len);
2421 else {
2422 /* empty trailer */
2423 }
2424 prot_puts(httpd_out, "\r\n");
24252456 }
24262457 else {
2427 /* full body or HTTP/1.0 close-delimited data */
2428 prot_write(httpd_out, buf, len);
2458 /* Full body or HTTP/1.0 close-delimited body */
2459 prot_write(httpd_out, buf + offset, outlen);
24292460 }
24302461 }
24312462
28802911 buf_appendmap(&txn->buf, /* buffered input */
28812912 (const char *) httpd_in->ptr, httpd_in->cnt);
28822913
2883 /* Close IP-based telemetry log */
28842914 if (httpd_logfd != -1) {
2885 /* Rewind log to current request and overwrite with redacted version */
2886 ftruncate(httpd_logfd,
2887 lseek(httpd_logfd, -buf_len(&txn->buf), SEEK_END));
2888 write(httpd_logfd, buf_cstring(&txn->buf), buf_len(&txn->buf));
2915 /* Rewind log to current request and truncate it */
2916 off_t end = lseek(httpd_logfd, 0, SEEK_END);
2917
2918 ftruncate(httpd_logfd, end - buf_len(&txn->buf));
2919 }
2920
2921 if (!proxy_userid || strcmp(proxy_userid, httpd_userid)) {
2922 /* Close existing telemetry log */
28892923 close(httpd_logfd);
2890 }
2891
2892 /* Create new telemetry log based on userid */
2893 httpd_logfd = telemetry_log(httpd_userid, httpd_in, httpd_out, 0);
2924
2925 prot_setlog(httpd_in, PROT_NO_FD);
2926 prot_setlog(httpd_out, PROT_NO_FD);
2927
2928 /* Create telemetry log based on new userid */
2929 httpd_logfd = telemetry_log(httpd_userid, httpd_in, httpd_out, 0);
2930 }
2931
28942932 if (httpd_logfd != -1) {
28952933 /* Log credential-redacted request */
28962934 write(httpd_logfd, buf_cstring(&txn->buf), buf_len(&txn->buf));
28972935 }
28982936
28992937 buf_reset(&txn->buf);
2938
2939 /* Make a copy of the external userid for use in proxying */
2940 if (proxy_userid) free(proxy_userid);
2941 proxy_userid = xstrdup(httpd_userid);
2942
2943 /* Translate any separators in userid */
2944 mboxname_hiersep_tointernal(&httpd_namespace, httpd_userid,
2945 config_virtdomains ?
2946 strcspn(httpd_userid, "@") : 0);
29002947
29012948 /* Do any namespace specific post-auth processing */
29022949 for (i = 0; namespaces[i]; i++) {
30973144 new = xzmalloc(sizeof(struct range));
30983145 new->first = first;
30993146 new->last = last;
3100 new->len = len;
31013147
31023148 if (tail) tail->next = new;
31033149 else *ranges = new;
31053151 }
31063152
31073153 tok_fini(&tok);
3108
3109 if (ret == HTTP_UNSAT_RANGE) {
3110 *ranges = new = xzmalloc(sizeof(struct range));
3111 new->len = len;
3112 }
31133154
31143155 return ret;
31153156 }
31213162 * Section 5 of HTTPbis, Part 4.
31223163 */
31233164 int check_precond(struct transaction_t *txn, const void *data,
3124 const char *etag, time_t lastmod, unsigned long len)
3165 const char *etag, time_t lastmod)
31253166 {
31263167 const char *lock_token = NULL;
31273168 unsigned locked = 0;
32293270 /* Step 5 */
32303271 if (txn->flags.ranges && /* Only if we support Range requests */
32313272 txn->meth == METH_GET && (hdr = spool_getheader(hdrcache, "Range"))) {
3232 const char *ranges = hdr[0];
32333273
32343274 if ((hdr = spool_getheader(hdrcache, "If-Range"))) {
32353275 since = message_parse_date((char *) hdr[0],
32393279
32403280 /* Only process Range if If-Range isn't present or validator matches */
32413281 if (!hdr || (since && (lastmod <= since)) || !etagcmp(hdr[0], etag))
3242 return parse_ranges(ranges, len, &txn->resp_body.range);
3282 return HTTP_PARTIAL;
32433283 }
32443284
32453285 /* Step 6 */
32823322 buf_printf(body, "\r\n--%s\r\n"
32833323 "Content-Type: %s\r\n"
32843324 "Content-Range: bytes %lu-%lu/%lu\r\n\r\n",
3285 boundary, type, range->first, range->last, range->len);
3325 boundary, type, range->first, range->last,
3326 txn->resp_body.len);
32863327 write_body(0, txn, buf_cstring(body), buf_len(body));
32873328
32883329 /* Output range data */
33013342 /* End of output */
33023343 write_body(0, txn, NULL, 0);
33033344 }
3345
3346 const struct mimetype {
3347 const char *ext;
3348 const char *type;
3349 unsigned int compressible;
3350 } mimetypes[] = {
3351 { ".css", "text/css", 1 },
3352 { ".htm", "text/html", 1 },
3353 { ".html", "text/html", 1 },
3354 { ".text", "text/plain", 1 },
3355 { ".txt", "text/plain", 1 },
3356
3357 { ".gif", "image/gif", 0 },
3358 { ".jpg", "image/jpeg", 0 },
3359 { ".jpeg", "image/jpeg", 0 },
3360 { ".png", "image/png", 0 },
3361
3362 { ".svg", "image/svg+xml", 1 },
3363 { ".tif", "image/tiff", 1 },
3364 { ".tiff", "image/tiff", 1 },
3365
3366 { ".bz", "application/x-bzip", 0 },
3367 { ".bz2", "application/x-bzip2", 0 },
3368 { ".gz", "application/gzip", 0 },
3369 { ".gzip", "application/gzip", 0 },
3370 { ".tgz", "application/gzip", 0 },
3371 { ".zip", "application/zip", 0 },
3372
3373 { ".doc", "application/msword", 1 },
3374 { ".js", "application/javascript", 1 },
3375 { ".pdf", "application/pdf", 1 },
3376 { ".ppt", "application/vnd.ms-powerpoint", 1 },
3377 { ".sh", "application/x-sh", 1 },
3378 { ".tar", "application/x-tar", 1 },
3379 { ".xls", "application/vnd.ms-excel", 1 },
3380 { ".xml", "application/xml", 1 },
3381
3382 { NULL, NULL, 0 }
3383 };
33043384
33053385
33063386 /* Perform a GET/HEAD request */
33123392 static struct buf pathbuf = BUF_INITIALIZER;
33133393 struct stat sbuf;
33143394 const char *msg_base = NULL;
3315 unsigned long msg_size = 0, offset = 0, datalen;
3395 unsigned long msg_size = 0;
33163396 struct resp_body_t *resp_body = &txn->resp_body;
33173397
33183398 /* Serve up static pages */
33423422
33433423 /* See if file exists and get Content-Length & Last-Modified time */
33443424 if (r || !S_ISREG(sbuf.st_mode)) return HTTP_NOT_FOUND;
3345
3346 datalen = sbuf.st_size;
3347
3348 /* Generate Etag */
3349 assert(!buf_len(&txn->buf));
3350 buf_printf(&txn->buf, "%ld-%ld", (long) sbuf.st_mtime, (long) sbuf.st_size);
3351
3352 /* Check any preconditions, including range request */
3353 txn->flags.ranges = !txn->flags.ce;
3354 precond = check_precond(txn, NULL, buf_cstring(&txn->buf), sbuf.st_mtime,
3355 datalen);
3356
3357 switch (precond) {
3358 case HTTP_OK:
3359 break;
3360
3361 case HTTP_PARTIAL:
3362 /* Set data parameters for range */
3363 offset += resp_body->range->first;
3364 datalen = resp_body->range->last - resp_body->range->first + 1;
3365 break;
3366
3367 case HTTP_NOT_MODIFIED:
3368 /* Fill in ETag for 304 response */
3369 resp_body->etag = buf_cstring(&txn->buf);
3370
3371 default:
3372 /* We failed a precondition - don't perform the request */
3373 return precond;
3374 }
3375
3376 if (txn->meth == METH_GET) {
3377 /* Open and mmap the file */
3378 if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR;
3379 map_refresh(fd, 1, &msg_base, &msg_size, sbuf.st_size, path, NULL);
3380 }
3381
3382 /* Fill in ETag and Last-Modified */
3383 resp_body->etag = buf_cstring(&txn->buf);
3384 resp_body->lastmod = sbuf.st_mtime;
33853425
33863426 if (!resp_body->type) {
33873427 /* Caller hasn't specified the Content-Type */
33893429
33903430 if ((ext = strrchr(path, '.'))) {
33913431 /* Try to use filename extension to identity Content-Type */
3392 if (!strcasecmp(ext, ".text") || !strcmp(ext, ".txt"))
3393 resp_body->type = "text/plain";
3394 else if (!strcasecmp(ext, ".html") || !strcmp(ext, ".htm"))
3395 resp_body->type = "text/html";
3396 else if (!strcasecmp(ext, ".css"))
3397 resp_body->type = "text/css";
3398 else if (!strcasecmp(ext, ".js"))
3399 resp_body->type = "text/javascript";
3400 else if (!strcasecmp(ext, ".jpeg") || !strcmp(ext, ".jpg"))
3401 resp_body->type = "image/jpeg";
3402 else if (!strcasecmp(ext, ".gif"))
3403 resp_body->type = "image/gif";
3404 else if (!strcasecmp(ext, ".png"))
3405 resp_body->type = "image/png";
3406 else if (!strcasecmp(ext, ".svg"))
3407 resp_body->type = "image/svg+xml";
3408 else if (!strcasecmp(ext, ".tiff") || !strcmp(ext, ".tif"))
3409 resp_body->type = "image/tiff";
3410 }
3411 }
3412
3413 if (resp_body->range && resp_body->range->next) {
3414 /* multiple ranges */
3415 multipart_byteranges(txn, msg_base);
3416 }
3417 else write_body(precond, txn, msg_base + offset, datalen);
3432 const struct mimetype *mtype;
3433
3434 for (mtype = mimetypes; mtype->ext; mtype++) {
3435 if (!strcasecmp(ext, mtype->ext)) {
3436 resp_body->type = mtype->type;
3437 if (!mtype->compressible) {
3438 /* Never compress non-compressible resources */
3439 txn->resp_body.enc = CE_IDENTITY;
3440 txn->flags.te = TE_NONE;
3441 txn->flags.vary &= ~VARY_AE;
3442 }
3443 break;
3444 }
3445 }
3446 }
3447 }
3448
3449 /* Generate Etag */
3450 assert(!buf_len(&txn->buf));
3451 buf_printf(&txn->buf, "%ld-%ld", (long) sbuf.st_mtime, (long) sbuf.st_size);
3452
3453 /* Check any preconditions, including range request */
3454 txn->flags.ranges = 1;
3455 precond = check_precond(txn, NULL, buf_cstring(&txn->buf), sbuf.st_mtime);
3456
3457 switch (precond) {
3458 case HTTP_OK:
3459 case HTTP_PARTIAL:
3460 case HTTP_NOT_MODIFIED:
3461 /* Fill in ETag, Last-Modified, and Expires */
3462 resp_body->etag = buf_cstring(&txn->buf);
3463 resp_body->lastmod = sbuf.st_mtime;
3464 resp_body->maxage = 86400; /* 24 hrs */
3465 txn->flags.cc |= CC_MAXAGE;
3466 if (httpd_userid) txn->flags.cc |= CC_PUBLIC;
3467
3468 if (precond != HTTP_NOT_MODIFIED) break;
3469
3470 default:
3471 /* We failed a precondition - don't perform the request */
3472 resp_body->type = NULL;
3473 return precond;
3474 }
3475
3476 if (txn->meth == METH_GET) {
3477 /* Open and mmap the file */
3478 if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR;
3479 map_refresh(fd, 1, &msg_base, &msg_size, sbuf.st_size, path, NULL);
3480 }
3481
3482 write_body(precond, txn, msg_base, sbuf.st_size);
34183483
34193484 if (fd != -1) {
34203485 map_free(&msg_base, &msg_size);
35643629 /* Remote mailbox */
35653630 struct backend *be;
35663631
3567 be = proxy_findserver(server, &http_protocol, httpd_userid,
3632 be = proxy_findserver(server, &http_protocol, proxy_userid,
35683633 &backend_cached, NULL, NULL, httpd_in);
35693634 if (!be) return HTTP_UNAVAILABLE;
35703635
5656
5757 #define MAX_REQ_LINE 8000 /* minimum size per HTTPbis */
5858 #define MARKUP_INDENT 2 /* # spaces to indent each line of markup */
59 #define GZIP_MIN_LEN 300 /* minimum length of data to gzip */
5960
6061 /* Supported HTTP version */
6162 #define HTTP_VERSION "HTTP/1.1"
9293 unsigned flags;
9394 };
9495 extern const struct known_meth_t http_methods[];
96
97 /* Flags for known methods*/
98 enum {
99 METH_NOBODY = (1<<0), /* Method does not expect a body */
100 };
95101
96102 /* Index into known HTTP methods - needs to stay in sync with array */
97103 enum {
228234 struct range {
229235 unsigned long first;
230236 unsigned long last;
231 unsigned long len;
232237 struct range *next;
233238 };
234239
236241 struct resp_body_t {
237242 ulong len; /* Content-Length */
238243 struct range *range;/* Content-Range */
239 const char *enc; /* Content-Encoding */
244 unsigned char enc; /* Content-Encoding */
240245 const char *lang; /* Content-Language */
241246 const char *loc; /* Content-Location */
242247 const char *type; /* Content-Type */
244249 const char *lock; /* Lock-Token */
245250 const char *etag; /* ETag */
246251 time_t lastmod; /* Last-Modified */
252 time_t maxage; /* Expires */
247253 const char *stag; /* Schedule-Tag */
248254 time_t iserial; /* iSched serial# */
249255 struct buf payload; /* Payload */
256262 unsigned char cors; /* Cross-Origin Resource Sharing */
257263 unsigned char body; /* read_body() flags on req */
258264 unsigned char te; /* Transfer-Encoding for resp */
259 unsigned char ce; /* Content-Encoding for resp */
260265 unsigned char cc; /* Cache-Control directives for resp */
261266 unsigned char ranges; /* Accept range requests for resource */
262267 unsigned char vary; /* Headers on which response varied */
277282 struct resp_body_t resp_body; /* Response body meta-data */
278283 #ifdef HAVE_ZLIB
279284 z_stream zstrm; /* Compression context */
285 struct buf zbuf; /* Compression buffer */
280286 #endif
281287 struct buf buf; /* Working buffer - currently used for:
282288 httpd:
320326 /* Transfer-Encoding flags (coding of response payload) */
321327 enum {
322328 TE_NONE = 0,
323 TE_CHUNKED = (1<<0),
329 TE_DEFLATE = (1<<0), /* Implies TE_CHUNKED as final coding */
324330 TE_GZIP = (1<<1), /* Implies TE_CHUNKED as final coding */
325 TE_DEFLATE = (1<<2) /* Implies TE_CHUNKED as final coding */
331 TE_CHUNKED = (1<<2) /* MUST be last */
326332 };
327333
328334 /* Content-Encoding flags (coding of representation) */
329335 enum {
330336 CE_IDENTITY = 0,
331 CE_GZIP = 1,
332 CE_DEFLATE = 2
337 CE_DEFLATE = (1<<0),
338 CE_GZIP = (1<<1)
333339 };
334340
335341 /* Cache-Control directive flags */
336342 enum {
337 CC_NOCACHE = (1<<0),
338 CC_NOTRANSFORM = (1<<1),
339 CC_PRIVATE = (1<<2)
343 CC_REVALIDATE = (1<<0),
344 CC_NOCACHE = (1<<1),
345 CC_NOSTORE = (1<<2),
346 CC_NOTRANSFORM = (1<<3),
347 CC_PUBLIC = (1<<4),
348 CC_PRIVATE = (1<<5),
349 CC_MAXAGE = (1<<6)
340350 };
341351
342352 /* Vary header flags (headers used in selecting/producing representation) */
394404 extern int httpd_timeout;
395405 extern int httpd_userisadmin;
396406 extern int httpd_userisproxyadmin;
397 extern char *httpd_userid;
407 extern char *httpd_userid, *proxy_userid;
398408 extern struct auth_state *httpd_authstate;
399409 extern struct namespace httpd_namespace;
400410 extern struct sockaddr_storage httpd_localaddr, httpd_remoteaddr;
408418 int locktype);
409419 extern const char *http_statusline(long code);
410420 extern void httpdate_gen(char *buf, size_t len, time_t t);
411 extern void comma_list_hdr(const char *hdr, const char *vals[], unsigned flags);
421 extern void comma_list_hdr(const char *hdr, const char *vals[],
422 unsigned flags, ...);
412423 extern void response_header(long code, struct transaction_t *txn);
413424 extern void buf_printf_markup(struct buf *buf, unsigned level,
414425 const char *fmt, ...);
424435 extern int meth_trace(struct transaction_t *txn, void *params);
425436 extern int etagcmp(const char *hdr, const char *etag);
426437 extern int check_precond(struct transaction_t *txn, const void *data,
427 const char *etag, time_t lastmod, unsigned long len);
438 const char *etag, time_t lastmod);
428439 extern int read_body(struct protstream *pin, hdrcache_t hdrs, struct buf *body,
429440 unsigned char *flags, const char **errstr);
430441
11991199 set (the default), the value of the "servername" option will be
12001200 used.*/
12011201
1202 { "rss_ttl", 30, INT }
1203 /* RSS channel time to live. Indicates to the client the number of
1204 minutes that a channel can be cached before refreshing from the
1205 source. If set to 0, no indication will be made to the client.
1206 The default is 30 minutes. */
1207
1208 { "rss_webmaster", NULL, STRING }
1209 /* Email address of the person responsible for technical issues
1210 with the RSS feeds. If not set (the default), no webMaster
1211 will be included in the feeds. */
1212
12131202 # Commented out - used by libsasl
12141203 # { "sasl_auto_transition", 0, SWITCH }
12151204 /* If enabled, the SASL library will automatically create authentication
709709 {(void*)0},
710710 { { NULL, IMAP_ENUM_ZERO } } },
711711 { IMAPOPT_RSS_REALM, "rss_realm", 0, OPT_STRING,
712 {(void *)(NULL)},
713 { { NULL, IMAP_ENUM_ZERO } } },
714 { IMAPOPT_RSS_TTL, "rss_ttl", 0, OPT_INT,
715 {(void*)30},
716 { { NULL, IMAP_ENUM_ZERO } } },
717 { IMAPOPT_RSS_WEBMASTER, "rss_webmaster", 0, OPT_STRING,
718712 {(void *)(NULL)},
719713 { { NULL, IMAP_ENUM_ZERO } } },
720714 { IMAPOPT_SASL_MAXIMUM_LAYER, "sasl_maximum_layer", 0, OPT_INT,
213213 IMAPOPT_RSS_MAXITEMS,
214214 IMAPOPT_RSS_MAXSYNOPSIS,
215215 IMAPOPT_RSS_REALM,
216 IMAPOPT_RSS_TTL,
217 IMAPOPT_RSS_WEBMASTER,
218216 IMAPOPT_SASL_MAXIMUM_LAYER,
219217 IMAPOPT_SASL_MINIMUM_LAYER,
220218 IMAPOPT_SEENSTATE_DB,
12081208 int prot_printf(struct protstream *s, const char *fmt, ...)
12091209 {
12101210 va_list pvar;
1211 int r;
1212
1213 va_start(pvar, fmt);
1214 r = prot_vprintf(s, fmt, pvar);
1215 va_end(pvar);
1216
1217 return r;
1218 }
1219
1220 int prot_vprintf(struct protstream *s, const char *fmt, va_list pvar)
1221 {
12111222 char *percent, *p;
12121223 long l;
12131224 unsigned long ul;
12141225 int i;
12151226 unsigned u;
12161227 char buf[30];
1217 va_start(pvar, fmt);
12181228
12191229 assert(s->write);
12201230
13641374 fmt = percent+1;
13651375 }
13661376 prot_write(s, fmt, strlen(fmt));
1367 va_end(pvar);
13681377 if (s->error || s->eof) return EOF;
13691378 return 0;
13701379 }
251251 extern int prot_write(struct protstream *s, const char *buf, unsigned len);
252252 extern int prot_putbuf(struct protstream *s, struct buf *buf);
253253 extern int prot_puts(struct protstream *s, const char *str);
254 extern int prot_vprintf(struct protstream *, const char *, va_list);
254255 extern int prot_printf(struct protstream *, const char *, ...)
255256 #ifdef __GNUC__
256257 __attribute__ ((format (printf, 2, 3)));
10221022 The realm to present for HTTP authentication of RSS feeds. If not
10231023 set (the default), the value of the "servername" option will be
10241024 used.
1025 .IP "\fBrss_ttl:\fR 30" 5
1026 RSS channel time to live. Indicates to the client the number of
1027 minutes that a channel can be cached before refreshing from the
1028 source. If set to 0, no indication will be made to the client.
1029 The default is 30 minutes.
1030 .IP "\fBrss_webmaster:\fR <none>" 5
1031 Email address of the person responsible for technical issues
1032 with the RSS feeds. If not set (the default), no webMaster
1033 will be included in the feeds.
10341025 .IP "\fBsasl_auto_transition:\fR 0" 5
10351026 If enabled, the SASL library will automatically create authentication
10361027 secrets when given a plaintext password. See the SASL documentation.
123123 .\" ========================================================================
124124 .\"
125125 .IX Title "SIEVESHELL 1"
126 .TH SIEVESHELL 1 "2013-05-30" "perl v5.16.3" "User Contributed Perl Documentation"
126 .TH SIEVESHELL 1 "2013-07-01" "perl v5.16.3" "User Contributed Perl Documentation"
127127 .\" For nroff, turn off justification. Always turn off hyphenation; it makes
128128 .\" way too many mistakes in technical documents.
129129 .if n .ad l
0 /* Release cyrus-imapd-2.4.17-caldav-beta5 */
1 #define _CYRUS_VERSION "v2.4.17-caldav-beta5"
2 #define CYRUS_GITVERSION "8c94e19e 2013-05-30"
0 /* Release cyrus-imapd-2.4.17-caldav-beta6 */
1 #define _CYRUS_VERSION "v2.4.17-caldav-beta6"
2 #define CYRUS_GITVERSION "5e525dd0 2013-07-01"