Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F120822265
http_timezone.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
41 KB
Referenced Files
None
Subscribers
None
http_timezone.c
View Options
/* http_timezone.c -- Routines for handling timezone service requests in httpd
*
* Copyright (c) 1994-2014 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
/*
* TODO:
* - Implement localized names and "lang" parameter
*/
#include
<config.h>
#ifdef HAVE_UNISTD_H
#include
<unistd.h>
#endif
#include
<ctype.h>
#include
<string.h>
#include
<syslog.h>
#include
<assert.h>
#include
"global.h"
#include
"hash.h"
#include
"httpd.h"
#include
"http_dav.h"
#include
"http_err.h"
#include
"http_proxy.h"
#include
"jcal.h"
#include
"map.h"
#include
"tok.h"
#include
"strhash.h"
#include
"tz_err.h"
#include
"util.h"
#include
"version.h"
#include
"xcal.h"
#include
"xstrlcpy.h"
#include
"zoneinfo_db.h"
#define TIMEZONE_WELLKNOWN_URI "/.well-known/timezone"
static
time_t
compile_time
;
static
void
timezone_init
(
struct
buf
*
serverinfo
);
static
void
timezone_shutdown
(
void
);
static
int
meth_get
(
struct
transaction_t
*
txn
,
void
*
params
);
static
int
action_capa
(
struct
transaction_t
*
txn
);
static
int
action_list
(
struct
transaction_t
*
txn
);
static
int
action_get
(
struct
transaction_t
*
txn
);
static
int
action_expand
(
struct
transaction_t
*
txn
);
static
int
json_response
(
int
code
,
struct
transaction_t
*
txn
,
json_t
*
root
,
char
**
resp
);
static
int
json_error_response
(
struct
transaction_t
*
txn
,
long
tz_code
,
struct
strlist
*
param
,
icaltimetype
*
time
);
struct
observance
{
const
char
*
name
;
icaltimetype
onset
;
int
offset_from
;
int
offset_to
;
int
is_daylight
;
};
static
const
struct
action_t
{
const
char
*
name
;
int
(
*
proc
)(
struct
transaction_t
*
txn
);
}
actions
[]
=
{
{
"capabilities"
,
&
action_capa
},
{
"list"
,
&
action_list
},
{
"get"
,
&
action_get
},
{
"expand"
,
&
action_expand
},
{
"find"
,
&
action_list
},
{
NULL
,
NULL
}
};
static
struct
mime_type_t
tz_mime_types
[]
=
{
/* First item MUST be the default type and storage format */
{
"text/calendar; charset=utf-8"
,
"2.0"
,
"ics"
,
"ifb"
,
(
char
*
(
*
)(
void
*
))
&
icalcomponent_as_ical_string_r
,
NULL
,
NULL
,
NULL
,
NULL
},
{
"application/calendar+xml; charset=utf-8"
,
NULL
,
"xcs"
,
"xfb"
,
(
char
*
(
*
)(
void
*
))
&
icalcomponent_as_xcal_string
,
NULL
,
NULL
,
NULL
,
NULL
},
{
"application/calendar+json; charset=utf-8"
,
NULL
,
"jcs"
,
"jfb"
,
(
char
*
(
*
)(
void
*
))
&
icalcomponent_as_jcal_string
,
NULL
,
NULL
,
NULL
,
NULL
},
{
NULL
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
}
};
/* Namespace for TIMEZONE feeds of mailboxes */
struct
namespace_t
namespace_timezone
=
{
URL_NS_TIMEZONE
,
0
,
"/timezone"
,
TIMEZONE_WELLKNOWN_URI
,
0
/* auth */
,
ALLOW_READ
,
timezone_init
,
NULL
,
NULL
,
timezone_shutdown
,
{
{
NULL
,
NULL
},
/* ACL */
{
NULL
,
NULL
},
/* COPY */
{
NULL
,
NULL
},
/* DELETE */
{
&
meth_get
,
NULL
},
/* GET */
{
&
meth_get
,
NULL
},
/* HEAD */
{
NULL
,
NULL
},
/* LOCK */
{
NULL
,
NULL
},
/* MKCALENDAR */
{
NULL
,
NULL
},
/* MKCOL */
{
NULL
,
NULL
},
/* MOVE */
{
&
meth_options
,
NULL
},
/* OPTIONS */
{
NULL
,
NULL
},
/* POST */
{
NULL
,
NULL
},
/* PROPFIND */
{
NULL
,
NULL
},
/* PROPPATCH */
{
NULL
,
NULL
},
/* PUT */
{
NULL
,
NULL
},
/* REPORT */
{
&
meth_trace
,
NULL
},
/* TRACE */
{
NULL
,
NULL
}
/* UNLOCK */
}
};
static
void
timezone_init
(
struct
buf
*
serverinfo
__attribute__
((
unused
)))
{
namespace_timezone
.
enabled
=
config_httpmodules
&
IMAP_ENUM_HTTPMODULES_TIMEZONE
;
if
(
!
namespace_timezone
.
enabled
)
return
;
/* Open zoneinfo db */
if
(
zoneinfo_open
(
NULL
))
{
namespace_timezone
.
enabled
=
0
;
return
;
}
compile_time
=
calc_compile_time
(
__TIME__
,
__DATE__
);
initialize_tz_error_table
();
}
static
void
timezone_shutdown
(
void
)
{
zoneinfo_close
(
NULL
);
}
/* Perform a GET/HEAD request */
static
int
meth_get
(
struct
transaction_t
*
txn
,
void
*
params
__attribute__
((
unused
)))
{
int
ret
;
struct
strlist
*
action
;
const
struct
action_t
*
ap
=
NULL
;
action
=
hash_lookup
(
"action"
,
&
txn
->
req_qparams
);
if
(
action
&&
!
action
->
next
/* mandatory, once only */
)
{
for
(
ap
=
actions
;
ap
->
name
&&
strcmp
(
action
->
s
,
ap
->
name
);
ap
++
);
}
if
(
!
ap
||
!
ap
->
name
)
ret
=
json_error_response
(
txn
,
TZ_INVALID_ACTION
,
action
,
NULL
);
else
ret
=
ap
->
proc
(
txn
);
return
ret
;
}
/* Perform a capabilities action */
static
int
action_capa
(
struct
transaction_t
*
txn
)
{
int
precond
;
struct
message_guid
guid
;
const
char
*
etag
;
static
time_t
lastmod
=
0
;
static
char
*
resp
=
NULL
;
json_t
*
root
=
NULL
;
/* Generate ETag based on compile date/time of this source file.
* Extend this to include config file size/mtime if we add run-time options.
*/
assert
(
!
buf_len
(
&
txn
->
buf
));
buf_printf
(
&
txn
->
buf
,
"%ld"
,
(
long
)
compile_time
);
message_guid_generate
(
&
guid
,
buf_cstring
(
&
txn
->
buf
),
buf_len
(
&
txn
->
buf
));
etag
=
message_guid_encode
(
&
guid
);
/* Check any preconditions, including range request */
txn
->
flags
.
ranges
=
1
;
precond
=
check_precond
(
txn
,
NULL
,
etag
,
compile_time
);
switch
(
precond
)
{
case
HTTP_OK
:
case
HTTP_PARTIAL
:
case
HTTP_NOT_MODIFIED
:
/* Fill in Etag, Last-Modified, Expires */
txn
->
resp_body
.
etag
=
etag
;
txn
->
resp_body
.
lastmod
=
compile_time
;
txn
->
resp_body
.
maxage
=
86400
;
/* 24 hrs */
txn
->
flags
.
cc
|=
CC_MAXAGE
;
if
(
precond
!=
HTTP_NOT_MODIFIED
)
break
;
default
:
/* We failed a precondition - don't perform the request */
return
precond
;
}
if
(
txn
->
resp_body
.
lastmod
>
lastmod
)
{
struct
zoneinfo
info
;
int
r
;
/* Get info record from the database */
if
((
r
=
zoneinfo_lookup_info
(
&
info
)))
return
HTTP_SERVER_ERROR
;
/* Construct our response */
root
=
json_pack
(
"{s:i"
/* version */
" s:{"
/* info */
" s:s"
/* primary-source */
" s:{s:b s:b}"
/* truncated */
" s:s"
/* provider-details */
" s:[]"
/* contacts */
" }"
" s:["
/* actions */
" {s:s s:[]}"
/* capabilities */
" {s:s s:["
/* list */
// " {s:s s:b s:b}" /* lang */
" {s:s s:b s:b}"
/* tzid */
" {s:s s:b s:b}"
/* changedsince */
" ]}"
" {s:s s:["
/* get */
// " {s:s s:b s:b}" /* lang */
" {s:s s:b s:b}"
/* tzid */
" {s:s s:b s:b s:[s s s]}"
/* format */
" {s:s s:b s:b}"
/* truncate */
" ]}"
" {s:s s:["
/* expand */
// " {s:s s:b s:b}" /* lang */
" {s:s s:b s:b}"
/* tzid */
" {s:s s:b s:b}"
/* changedsince */
" {s:s s:b s:b}"
/* start */
" {s:s s:b s:b}"
/* end */
" ]}"
" {s:s s:["
/* find */
// " {s:s s:b s:b}" /* lang */
" {s:s s:b s:b}"
/* pattern */
" ]}"
" ]}"
,
"version"
,
1
,
"info"
,
"primary-source"
,
info
.
data
->
s
,
"truncated"
,
"any"
,
1
,
"untruncated"
,
1
,
"provider-details"
,
""
,
"contacts"
,
"actions"
,
"name"
,
"capabilities"
,
"parameters"
,
"name"
,
"list"
,
"parameters"
,
// "name", "lang", "required", 0, "multi", 1,
"name"
,
"tzid"
,
"required"
,
0
,
"multi"
,
1
,
"name"
,
"changedsince"
,
"required"
,
0
,
"multi"
,
0
,
"name"
,
"get"
,
"parameters"
,
// "name", "lang", "required", 0, "multi", 1,
"name"
,
"tzid"
,
"required"
,
1
,
"multi"
,
0
,
"name"
,
"format"
,
"required"
,
0
,
"multi"
,
0
,
"values"
,
"text/calendar"
,
"application/calendar+xml"
,
"application/calendar+json"
,
"name"
,
"truncate"
,
"required"
,
0
,
"multi"
,
0
,
"name"
,
"expand"
,
"parameters"
,
// "name", "lang", "required", 0, "multi", 1,
"name"
,
"tzid"
,
"required"
,
1
,
"multi"
,
0
,
"name"
,
"changedsince"
,
"required"
,
0
,
"multi"
,
0
,
"name"
,
"start"
,
"required"
,
1
,
"multi"
,
0
,
"name"
,
"end"
,
"required"
,
0
,
"multi"
,
0
,
"name"
,
"find"
,
"parameters"
,
// "name", "lang", "required", 0, "multi", 1,
"name"
,
"pattern"
,
"required"
,
1
,
"multi"
,
0
);
freestrlist
(
info
.
data
);
if
(
!
root
)
{
txn
->
error
.
desc
=
"Unable to create JSON response"
;
return
HTTP_SERVER_ERROR
;
}
/* Update lastmod */
lastmod
=
txn
->
resp_body
.
lastmod
;
}
/* Output the JSON object */
return
json_response
(
precond
,
txn
,
root
,
&
resp
);
}
struct
list_rock
{
json_t
*
tzarray
;
struct
hash_table
*
tztable
;
};
static
int
list_cb
(
const
char
*
tzid
,
int
tzidlen
,
struct
zoneinfo
*
zi
,
void
*
rock
)
{
struct
list_rock
*
lrock
=
(
struct
list_rock
*
)
rock
;
char
tzidbuf
[
200
],
lastmod
[
21
];
json_t
*
tz
;
if
(
lrock
->
tztable
)
{
if
(
hash_lookup
(
tzid
,
lrock
->
tztable
))
return
0
;
hash_insert
(
tzid
,
(
void
*
)
0xDEADBEEF
,
lrock
->
tztable
);
}
strlcpy
(
tzidbuf
,
tzid
,
tzidlen
+
1
);
rfc3339date_gen
(
lastmod
,
sizeof
(
lastmod
),
zi
->
dtstamp
);
tz
=
json_pack
(
"{s:s s:s}"
,
"tzid"
,
tzidbuf
,
"last-modified"
,
lastmod
);
json_array_append_new
(
lrock
->
tzarray
,
tz
);
if
(
zi
->
data
)
{
struct
strlist
*
sl
;
json_t
*
aliases
=
json_array
();
json_object_set_new
(
tz
,
"aliases"
,
aliases
);
for
(
sl
=
zi
->
data
;
sl
;
sl
=
sl
->
next
)
json_array_append_new
(
aliases
,
json_string
(
sl
->
s
));
}
return
0
;
}
/* Perform a list action */
static
int
action_list
(
struct
transaction_t
*
txn
)
{
int
r
,
precond
,
tzid_only
=
1
;
struct
strlist
*
param
,
*
name
=
NULL
;
icaltimetype
changedsince
=
icaltime_null_time
();
struct
resp_body_t
*
resp_body
=
&
txn
->
resp_body
;
struct
zoneinfo
info
;
time_t
lastmod
;
json_t
*
root
=
NULL
;
/* Sanity check the parameters */
param
=
hash_lookup
(
"action"
,
&
txn
->
req_qparams
);
if
(
!
strcmp
(
"find"
,
param
->
s
))
{
name
=
hash_lookup
(
"pattern"
,
&
txn
->
req_qparams
);
if
(
!
name
||
name
->
next
/* mandatory, once only */
||
!
name
->
s
||
!*
name
->
s
/* not empty */
||
!
strcspn
(
name
->
s
,
"*"
))
{
/* not (*)+ */
return
json_error_response
(
txn
,
TZ_INVALID_PATTERN
,
name
,
NULL
);
}
tzid_only
=
0
;
}
else
{
param
=
hash_lookup
(
"changedsince"
,
&
txn
->
req_qparams
);
if
(
param
)
{
changedsince
=
icaltime_from_string
(
param
->
s
);
if
(
param
->
next
||
!
changedsince
.
is_utc
)
{
/* once only, UTC */
return
json_error_response
(
txn
,
TZ_INVALID_CHANGEDSINCE
,
param
,
&
changedsince
);
}
}
name
=
hash_lookup
(
"tzid"
,
&
txn
->
req_qparams
);
if
(
name
)
{
if
(
changedsince
.
is_utc
)
{
return
json_error_response
(
txn
,
TZ_INVALID_TZID
,
param
,
&
changedsince
);
}
else
{
/* Check for tzid=*, and revert to empty list */
struct
strlist
*
sl
;
for
(
sl
=
name
;
sl
&&
strcmp
(
sl
->
s
,
"*"
);
sl
=
sl
->
next
);
if
(
sl
)
name
=
NULL
;
}
}
}
/* Get info record from the database */
if
((
r
=
zoneinfo_lookup_info
(
&
info
)))
return
HTTP_SERVER_ERROR
;
/* Generate ETag & Last-Modified from info record */
assert
(
!
buf_len
(
&
txn
->
buf
));
buf_printf
(
&
txn
->
buf
,
"%u-%ld"
,
strhash
(
info
.
data
->
s
),
info
.
dtstamp
);
lastmod
=
info
.
dtstamp
;
freestrlist
(
info
.
data
);
/* Check any preconditions, including range request */
txn
->
flags
.
ranges
=
1
;
precond
=
check_precond
(
txn
,
NULL
,
buf_cstring
(
&
txn
->
buf
),
lastmod
);
switch
(
precond
)
{
case
HTTP_OK
:
case
HTTP_PARTIAL
:
case
HTTP_NOT_MODIFIED
:
/* Fill in ETag, Last-Modified, and Expires */
resp_body
->
etag
=
buf_cstring
(
&
txn
->
buf
);
resp_body
->
lastmod
=
lastmod
;
resp_body
->
maxage
=
86400
;
/* 24 hrs */
txn
->
flags
.
cc
|=
CC_MAXAGE
|
CC_REVALIDATE
;
if
(
httpd_userid
)
txn
->
flags
.
cc
|=
CC_PUBLIC
;
if
(
precond
!=
HTTP_NOT_MODIFIED
)
break
;
default
:
/* We failed a precondition - don't perform the request */
resp_body
->
type
=
NULL
;
return
precond
;
}
if
(
txn
->
meth
!=
METH_HEAD
)
{
struct
list_rock
lrock
=
{
NULL
,
NULL
};
struct
hash_table
tzids
;
char
dtstamp
[
21
];
/* Start constructing our response */
rfc3339date_gen
(
dtstamp
,
sizeof
(
dtstamp
),
lastmod
);
root
=
json_pack
(
"{s:s s:[]}"
,
"dtstamp"
,
dtstamp
,
"timezones"
);
if
(
!
root
)
{
txn
->
error
.
desc
=
"Unable to create JSON response"
;
return
HTTP_SERVER_ERROR
;
}
lrock
.
tzarray
=
json_object_get
(
root
,
"timezones"
);
if
(
!
tzid_only
)
{
construct_hash_table
(
&
tzids
,
500
,
1
);
lrock
.
tztable
=
&
tzids
;
}
/* Add timezones to array */
do
{
zoneinfo_find
(
name
?
name
->
s
:
NULL
,
tzid_only
,
icaltime_as_timet
(
changedsince
),
&
list_cb
,
&
lrock
);
}
while
(
name
&&
(
name
=
name
->
next
));
if
(
!
tzid_only
)
free_hash_table
(
&
tzids
,
NULL
);
}
/* Output the JSON object */
return
json_response
(
precond
,
txn
,
root
,
NULL
);
}
static
void
check_tombstone
(
struct
observance
*
tombstone
,
struct
observance
*
obs
)
{
if
(
icaltime_compare
(
obs
->
onset
,
tombstone
->
onset
)
>
0
)
{
/* onset is closer to cutoff than existing tombstone */
tombstone
->
name
=
icalmemory_tmp_copy
(
obs
->
name
);
tombstone
->
offset_from
=
tombstone
->
offset_to
=
obs
->
offset_to
;
tombstone
->
is_daylight
=
obs
->
is_daylight
;
tombstone
->
onset
=
obs
->
onset
;
}
}
struct
rdate
{
icalproperty
*
prop
;
struct
icaldatetimeperiodtype
date
;
};
static
int
rdate_compare
(
const
void
*
rdate1
,
const
void
*
rdate2
)
{
return
icaltime_compare
(((
struct
rdate
*
)
rdate1
)
->
date
.
time
,
((
struct
rdate
*
)
rdate2
)
->
date
.
time
);
}
static
const
struct
observance
*
truncate_vtimezone
(
icalcomponent
*
vtz
,
icaltimetype
start
,
icaltimetype
end
,
icalarray
*
obsarray
)
{
icalcomponent
*
comp
,
*
nextc
,
*
tomb_std
=
NULL
,
*
tomb_day
=
NULL
;
icalproperty
*
prop
,
*
proleptic_prop
=
NULL
;
static
struct
observance
tombstone
;
unsigned
need_tomb
=
!
icaltime_is_null_time
(
start
);
/* See if we have a proleptic tzname in VTIMEZONE */
for
(
prop
=
icalcomponent_get_first_property
(
vtz
,
ICAL_X_PROPERTY
);
prop
;
prop
=
icalcomponent_get_next_property
(
vtz
,
ICAL_X_PROPERTY
))
{
if
(
!
strcmp
(
"X-PROLEPTIC-TZNAME"
,
icalproperty_get_x_name
(
prop
)))
{
proleptic_prop
=
prop
;
break
;
}
}
memset
(
&
tombstone
,
0
,
sizeof
(
struct
observance
));
tombstone
.
name
=
icalmemory_tmp_copy
(
proleptic_prop
?
icalproperty_get_x
(
proleptic_prop
)
:
"LMT"
);
/* Process each VTMEZONE STANDARD/DAYLIGHT subcomponent */
for
(
comp
=
icalcomponent_get_first_component
(
vtz
,
ICAL_ANY_COMPONENT
);
comp
;
comp
=
nextc
)
{
icalproperty
*
dtstart_prop
=
NULL
,
*
rrule_prop
=
NULL
;
icalarray
*
rdate_array
=
icalarray_new
(
sizeof
(
struct
rdate
),
10
);
icaltimetype
dtstart
;
struct
observance
obs
;
unsigned
n
,
trunc_dtstart
=
0
;
int
r
;
nextc
=
icalcomponent_get_next_component
(
vtz
,
ICAL_ANY_COMPONENT
);
memset
(
&
obs
,
0
,
sizeof
(
struct
observance
));
obs
.
offset_from
=
obs
.
offset_to
=
INT_MAX
;
obs
.
is_daylight
=
(
icalcomponent_isa
(
comp
)
==
ICAL_XDAYLIGHT_COMPONENT
);
/* Grab the properties that we require to expand recurrences */
for
(
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_ANY_PROPERTY
);
prop
;
prop
=
icalcomponent_get_next_property
(
comp
,
ICAL_ANY_PROPERTY
))
{
switch
(
icalproperty_isa
(
prop
))
{
case
ICAL_TZNAME_PROPERTY
:
obs
.
name
=
icalproperty_get_tzname
(
prop
);
break
;
case
ICAL_DTSTART_PROPERTY
:
dtstart_prop
=
prop
;
obs
.
onset
=
dtstart
=
icalproperty_get_dtstart
(
prop
);
break
;
case
ICAL_TZOFFSETFROM_PROPERTY
:
obs
.
offset_from
=
icalproperty_get_tzoffsetfrom
(
prop
);
break
;
case
ICAL_TZOFFSETTO_PROPERTY
:
obs
.
offset_to
=
icalproperty_get_tzoffsetto
(
prop
);
break
;
case
ICAL_RRULE_PROPERTY
:
rrule_prop
=
prop
;
break
;
case
ICAL_RDATE_PROPERTY
:
{
struct
rdate
rdate
=
{
prop
,
icalproperty_get_rdate
(
prop
)
};
icalarray_append
(
rdate_array
,
&
rdate
);
break
;
}
default
:
/* ignore all other properties */
break
;
}
}
/* We MUST have DTSTART, TZNAME, TZOFFSETFROM, and TZOFFSETTO */
if
(
!
dtstart_prop
||
!
obs
.
name
||
obs
.
offset_from
==
INT_MAX
||
obs
.
offset_to
==
INT_MAX
)
{
icalarray_free
(
rdate_array
);
continue
;
}
/* Adjust DTSTART observance to UTC */
icaltime_adjust
(
&
obs
.
onset
,
0
,
0
,
0
,
-
obs
.
offset_from
);
obs
.
onset
.
is_utc
=
1
;
/* Check DTSTART vs window close */
if
(
!
icaltime_is_null_time
(
end
)
&&
icaltime_compare
(
obs
.
onset
,
end
)
>=
0
)
{
/* All observances occur on/after window close - remove component */
icalcomponent_remove_component
(
vtz
,
comp
);
icalcomponent_free
(
comp
);
/* Nothing else to do */
icalarray_free
(
rdate_array
);
continue
;
}
/* Check DTSTART vs window open */
r
=
icaltime_compare
(
obs
.
onset
,
start
);
if
(
r
<
0
)
{
/* DTSTART is prior to our window open - check it vs tombstone */
if
(
need_tomb
)
check_tombstone
(
&
tombstone
,
&
obs
);
/* Adjust it */
trunc_dtstart
=
1
;
}
else
{
/* DTSTART is on/after our window open */
if
(
r
==
0
)
need_tomb
=
0
;
if
(
obsarray
&&
!
rrule_prop
)
{
/* Add the DTSTART observance to our array */
icalarray_append
(
obsarray
,
&
obs
);
}
}
if
(
rrule_prop
)
{
struct
icalrecurrencetype
rrule
=
icalproperty_get_rrule
(
rrule_prop
);
icalrecur_iterator
*
ritr
=
NULL
;
unsigned
infinite
=
icaltime_is_null_time
(
rrule
.
until
);
unsigned
trunc_until
=
0
;
/* Check RRULE duration */
if
(
!
infinite
&&
icaltime_compare
(
rrule
.
until
,
start
)
<
0
)
{
/* RRULE ends prior to our window open -
check UNTIL vs tombstone */
obs
.
onset
=
rrule
.
until
;
if
(
need_tomb
)
check_tombstone
(
&
tombstone
,
&
obs
);
/* Remove RRULE */
icalcomponent_remove_property
(
comp
,
rrule_prop
);
icalproperty_free
(
rrule_prop
);
}
else
{
/* RRULE ends on/after our window open */
if
(
!
icaltime_is_null_time
(
end
)
&&
(
infinite
||
icaltime_compare
(
rrule
.
until
,
end
)
>
0
))
{
/* RRULE ends after our window close - need to adjust it */
trunc_until
=
1
;
}
if
(
!
infinite
)
{
/* Adjust UNTIL to local time (for iterator) */
icaltime_adjust
(
&
rrule
.
until
,
0
,
0
,
0
,
obs
.
offset_from
);
rrule
.
until
.
is_utc
=
0
;
}
if
(
trunc_dtstart
)
{
/* Bump RRULE start to 1 year prior to our window open */
dtstart
.
year
=
start
.
year
-
1
;
}
ritr
=
icalrecur_iterator_new
(
rrule
,
dtstart
);
}
/* Process any RRULE observances within our window */
if
(
ritr
)
{
icaltimetype
recur
,
prev_onset
;
/* Mark original DTSTART (UTC) */
dtstart
=
obs
.
onset
;
while
(
!
icaltime_is_null_time
(
obs
.
onset
=
recur
=
icalrecur_iterator_next
(
ritr
)))
{
unsigned
ydiff
;
/* Adjust observance to UTC */
icaltime_adjust
(
&
obs
.
onset
,
0
,
0
,
0
,
-
obs
.
offset_from
);
obs
.
onset
.
is_utc
=
1
;
if
(
trunc_until
&&
icaltime_compare
(
obs
.
onset
,
end
)
>
0
)
{
/* Observance is on/after window close */
/* Check if DSTART is within 1yr of prev onset */
ydiff
=
prev_onset
.
year
-
dtstart
.
year
;
if
(
ydiff
<=
1
)
{
/* Remove RRULE */
icalcomponent_remove_property
(
comp
,
rrule_prop
);
icalproperty_free
(
rrule_prop
);
if
(
ydiff
)
{
/* Add previous onset as RDATE */
struct
icaldatetimeperiodtype
rdate
=
{
prev_onset
,
icalperiodtype_null_period
()
};
prop
=
icalproperty_new_rdate
(
rdate
);
icalcomponent_add_property
(
comp
,
prop
);
}
}
else
{
/* Set UNTIL to previous onset */
rrule
.
until
=
prev_onset
;
icalproperty_set_rrule
(
rrule_prop
,
rrule
);
}
/* We're done */
break
;
}
/* Check observance vs our window open */
r
=
icaltime_compare
(
obs
.
onset
,
start
);
if
(
r
<
0
)
{
/* Observance is prior to our window open -
check it vs tombstone */
if
(
need_tomb
)
check_tombstone
(
&
tombstone
,
&
obs
);
}
else
{
/* Observance is on/after our window open */
if
(
r
==
0
)
need_tomb
=
0
;
if
(
trunc_dtstart
)
{
/* Make this observance the new DTSTART */
icalproperty_set_dtstart
(
dtstart_prop
,
recur
);
dtstart
=
obs
.
onset
;
trunc_dtstart
=
0
;
/* Check if new DSTART is within 1yr of UNTIL */
ydiff
=
rrule
.
until
.
year
-
recur
.
year
;
if
(
!
trunc_until
&&
ydiff
<=
1
)
{
/* Remove RRULE */
icalcomponent_remove_property
(
comp
,
rrule_prop
);
icalproperty_free
(
rrule_prop
);
if
(
ydiff
)
{
/* Add UNTIL as RDATE */
struct
icaldatetimeperiodtype
rdate
=
{
rrule
.
until
,
icalperiodtype_null_period
()
};
prop
=
icalproperty_new_rdate
(
rdate
);
icalcomponent_add_property
(
comp
,
prop
);
}
}
}
if
(
obsarray
)
{
/* Add the observance to our array */
icalarray_append
(
obsarray
,
&
obs
);
}
else
if
(
!
trunc_until
)
{
/* We're done */
break
;
}
}
prev_onset
=
obs
.
onset
;
}
icalrecur_iterator_free
(
ritr
);
}
}
/* Sort the RDATEs by onset */
icalarray_sort
(
rdate_array
,
&
rdate_compare
);
/* Check RDATEs */
for
(
n
=
0
;
n
<
rdate_array
->
num_elements
;
n
++
)
{
struct
rdate
*
rdate
=
icalarray_element_at
(
rdate_array
,
n
);
if
(
n
==
0
&&
icaltime_compare
(
rdate
->
date
.
time
,
dtstart
)
==
0
)
{
/* RDATE is same as DTSTART - remove it */
icalcomponent_remove_property
(
comp
,
rdate
->
prop
);
icalproperty_free
(
rdate
->
prop
);
continue
;
}
obs
.
onset
=
rdate
->
date
.
time
;
/* Adjust observance to UTC */
icaltime_adjust
(
&
obs
.
onset
,
0
,
0
,
0
,
-
obs
.
offset_from
);
obs
.
onset
.
is_utc
=
1
;
if
(
!
icaltime_is_null_time
(
end
)
&&
icaltime_compare
(
obs
.
onset
,
end
)
>=
0
)
{
/* RDATE is after our window close - remove it */
icalcomponent_remove_property
(
comp
,
rdate
->
prop
);
icalproperty_free
(
rdate
->
prop
);
continue
;
}
r
=
icaltime_compare
(
obs
.
onset
,
start
);
if
(
r
<
0
)
{
/* RDATE is prior to window open - check it vs tombstone */
if
(
need_tomb
)
check_tombstone
(
&
tombstone
,
&
obs
);
/* Remove it */
icalcomponent_remove_property
(
comp
,
rdate
->
prop
);
icalproperty_free
(
rdate
->
prop
);
}
else
{
/* RDATE is on/after our window open */
if
(
r
==
0
)
need_tomb
=
0
;
if
(
trunc_dtstart
)
{
/* Make this RDATE the new DTSTART */
icalproperty_set_dtstart
(
dtstart_prop
,
rdate
->
date
.
time
);
trunc_dtstart
=
0
;
icalcomponent_remove_property
(
comp
,
rdate
->
prop
);
icalproperty_free
(
rdate
->
prop
);
}
if
(
obsarray
)
{
/* Add the observance to our array */
icalarray_append
(
obsarray
,
&
obs
);
}
}
}
icalarray_free
(
rdate_array
);
/* Final check */
if
(
trunc_dtstart
)
{
/* All observances in comp occur prior to window open, remove it
unless we haven't saved a tombstone comp of this type yet */
if
(
icalcomponent_isa
(
comp
)
==
ICAL_XDAYLIGHT_COMPONENT
)
{
if
(
!
tomb_day
)
{
tomb_day
=
comp
;
comp
=
NULL
;
}
}
else
if
(
!
tomb_std
)
{
tomb_std
=
comp
;
comp
=
NULL
;
}
if
(
comp
)
{
icalcomponent_remove_component
(
vtz
,
comp
);
icalcomponent_free
(
comp
);
}
}
}
if
(
need_tomb
&&
!
icaltime_is_null_time
(
tombstone
.
onset
))
{
/* Need to add tombstone component/observance starting at window open
as long as its not prior to start of TZ data */
icalcomponent
*
tomb
;
icalproperty
*
prop
,
*
nextp
;
if
(
obsarray
)
{
/* Add the tombstone to our array */
tombstone
.
onset
=
start
;
icalarray_append
(
obsarray
,
&
tombstone
);
}
/* Determine which tombstone component we need */
if
(
tombstone
.
is_daylight
)
{
tomb
=
tomb_day
;
tomb_day
=
NULL
;
}
else
{
tomb
=
tomb_std
;
tomb_std
=
NULL
;
}
/* Set property values on our tombstone */
for
(
prop
=
icalcomponent_get_first_property
(
tomb
,
ICAL_ANY_PROPERTY
);
prop
;
prop
=
nextp
)
{
nextp
=
icalcomponent_get_next_property
(
tomb
,
ICAL_ANY_PROPERTY
);
switch
(
icalproperty_isa
(
prop
))
{
case
ICAL_TZNAME_PROPERTY
:
icalproperty_set_tzname
(
prop
,
tombstone
.
name
);
break
;
case
ICAL_TZOFFSETFROM_PROPERTY
:
icalproperty_set_tzoffsetfrom
(
prop
,
tombstone
.
offset_from
);
break
;
case
ICAL_TZOFFSETTO_PROPERTY
:
icalproperty_set_tzoffsetto
(
prop
,
tombstone
.
offset_to
);
break
;
case
ICAL_DTSTART_PROPERTY
:
/* Adjust window open to local time */
icaltime_adjust
(
&
start
,
0
,
0
,
0
,
tombstone
.
offset_from
);
start
.
is_utc
=
0
;
icalproperty_set_dtstart
(
prop
,
start
);
break
;
default
:
icalcomponent_remove_property
(
tomb
,
prop
);
icalproperty_free
(
prop
);
break
;
}
}
/* Remove X-PROLEPTIC-TZNAME as it no longer applies */
if
(
proleptic_prop
)
{
icalcomponent_remove_property
(
vtz
,
proleptic_prop
);
icalproperty_free
(
proleptic_prop
);
}
}
/* Remove any unused tombstone components */
if
(
tomb_std
)
{
icalcomponent_remove_component
(
vtz
,
tomb_std
);
icalcomponent_free
(
tomb_std
);
}
if
(
tomb_day
)
{
icalcomponent_remove_component
(
vtz
,
tomb_day
);
icalcomponent_free
(
tomb_day
);
}
return
&
tombstone
;
}
#ifndef HAVE_TZDIST_PROPS
static
icalproperty
*
icalproperty_new_equivalenttzid
(
const
char
*
v
)
{
icalproperty
*
prop
=
icalproperty_new_x
(
v
);
icalproperty_set_x_name
(
prop
,
"EQUIVALENT-TZID"
);
return
prop
;
}
static
icalproperty
*
icalproperty_new_tzuntil
(
struct
icaltimetype
v
)
{
icalproperty
*
prop
=
icalproperty_new_x
(
icaltime_as_ical_string
(
v
));
icalproperty_set_x_name
(
prop
,
"TZUNTIL"
);
return
prop
;
}
#endif
/* HAVE_TZDIST_PROPS */
/* Perform a get action */
static
int
action_get
(
struct
transaction_t
*
txn
)
{
int
r
,
precond
;
struct
strlist
*
param
;
const
char
*
tzid
,
*
truncate
=
NULL
;
struct
zoneinfo
zi
;
time_t
lastmod
;
icaltimetype
start
=
icaltime_null_time
(),
end
=
icaltime_null_time
();
char
*
data
=
NULL
;
unsigned
long
datalen
=
0
;
struct
resp_body_t
*
resp_body
=
&
txn
->
resp_body
;
struct
mime_type_t
*
mime
=
NULL
;
/* Sanity check the parameters */
param
=
hash_lookup
(
"tzid"
,
&
txn
->
req_qparams
);
if
(
!
param
||
param
->
next
)
{
/* mandatory, once only */
return
json_error_response
(
txn
,
TZ_INVALID_TZID
,
param
,
NULL
);
}
if
(
strchr
(
param
->
s
,
'.'
))
{
/* paranoia */
return
json_error_response
(
txn
,
TZ_NOT_FOUND
,
NULL
,
NULL
);
}
tzid
=
param
->
s
;
/* Check/find requested MIME type */
param
=
hash_lookup
(
"format"
,
&
txn
->
req_qparams
);
if
(
param
&&
!
param
->
next
/* optional, once only */
)
{
for
(
mime
=
tz_mime_types
;
mime
->
content_type
;
mime
++
)
{
if
(
is_mediatype
(
param
->
s
,
mime
->
content_type
))
break
;
}
}
else
mime
=
tz_mime_types
;
if
(
!
mime
||
!
mime
->
content_type
)
{
return
json_error_response
(
txn
,
TZ_INVALID_FORMAT
,
param
,
NULL
);
}
/* Check for any truncation */
param
=
hash_lookup
(
"truncate"
,
&
txn
->
req_qparams
);
if
(
param
)
{
tok_t
tok
;
char
*
token
;
truncate
=
param
->
s
;
if
(
param
->
next
)
{
/* once only */
return
json_error_response
(
txn
,
TZ_INVALID_TRUNCATE
,
param
,
NULL
);
}
tok_init
(
&
tok
,
truncate
,
","
,
TOK_TRIMLEFT
|
TOK_TRIMRIGHT
|
TOK_EMPTY
);
token
=
tok_next
(
&
tok
);
if
(
!
token
||
(
strcmp
(
token
,
"*"
)
&&
!
icaltime_is_utc
((
start
=
icaltime_from_string
(
token
)))))
{
return
json_error_response
(
txn
,
TZ_INVALID_TRUNCATE
,
param
,
&
start
);
}
token
=
tok_next
(
&
tok
);
if
(
!
token
||
(
strcmp
(
token
,
"*"
)
&&
(
!
icaltime_is_utc
((
end
=
icaltime_from_string
(
token
)))
||
icaltime_compare
(
end
,
start
)
<=
0
)))
{
return
json_error_response
(
txn
,
TZ_INVALID_TRUNCATE
,
param
,
&
end
);
}
if
(
tok_next
(
&
tok
))
{
return
json_error_response
(
txn
,
TZ_INVALID_TRUNCATE
,
param
,
NULL
);
}
tok_fini
(
&
tok
);
}
/* Get info record from the database */
if
((
r
=
zoneinfo_lookup
(
tzid
,
&
zi
)))
{
return
(
r
==
CYRUSDB_NOTFOUND
?
json_error_response
(
txn
,
TZ_NOT_FOUND
,
NULL
,
NULL
)
:
HTTP_SERVER_ERROR
);
}
/* Generate ETag & Last-Modified from info record */
assert
(
!
buf_len
(
&
txn
->
buf
));
buf_printf
(
&
txn
->
buf
,
"%u-%ld"
,
strhash
(
tzid
),
zi
.
dtstamp
);
lastmod
=
zi
.
dtstamp
;
freestrlist
(
zi
.
data
);
/* Check any preconditions, including range request */
txn
->
flags
.
ranges
=
1
;
precond
=
check_precond
(
txn
,
NULL
,
buf_cstring
(
&
txn
->
buf
),
lastmod
);
switch
(
precond
)
{
case
HTTP_OK
:
case
HTTP_PARTIAL
:
case
HTTP_NOT_MODIFIED
:
/* Fill in Content-Type, ETag, Last-Modified, and Expires */
resp_body
->
type
=
mime
->
content_type
;
resp_body
->
etag
=
buf_cstring
(
&
txn
->
buf
);
resp_body
->
lastmod
=
lastmod
;
resp_body
->
maxage
=
86400
;
/* 24 hrs */
txn
->
flags
.
cc
|=
CC_MAXAGE
|
CC_REVALIDATE
;
if
(
httpd_userid
)
txn
->
flags
.
cc
|=
CC_PUBLIC
;
if
(
precond
!=
HTTP_NOT_MODIFIED
)
break
;
default
:
/* We failed a precondition - don't perform the request */
resp_body
->
type
=
NULL
;
return
precond
;
}
if
(
txn
->
meth
!=
METH_HEAD
)
{
static
struct
buf
pathbuf
=
BUF_INITIALIZER
;
const
char
*
path
,
*
proto
,
*
host
,
*
msg_base
=
NULL
;
unsigned
long
msg_size
=
0
;
icalcomponent
*
ical
,
*
vtz
;
icalproperty
*
prop
;
int
fd
;
/* Open, mmap, and parse the file */
buf_reset
(
&
pathbuf
);
buf_printf
(
&
pathbuf
,
"%s%s/%s.ics"
,
config_dir
,
FNAME_ZONEINFODIR
,
tzid
);
path
=
buf_cstring
(
&
pathbuf
);
if
((
fd
=
open
(
path
,
O_RDONLY
))
==
-1
)
return
HTTP_SERVER_ERROR
;
map_refresh
(
fd
,
1
,
&
msg_base
,
&
msg_size
,
MAP_UNKNOWN_LEN
,
path
,
NULL
);
if
(
!
msg_base
)
return
HTTP_SERVER_ERROR
;
ical
=
icalparser_parse_string
(
msg_base
);
map_free
(
&
msg_base
,
&
msg_size
);
close
(
fd
);
vtz
=
icalcomponent_get_first_component
(
ical
,
ICAL_VTIMEZONE_COMPONENT
);
prop
=
icalcomponent_get_first_property
(
vtz
,
ICAL_TZID_PROPERTY
);
if
(
zi
.
type
==
ZI_LINK
)
{
/* Add EQUIVALENT-TZID */
const
char
*
equiv
=
icalproperty_get_tzid
(
prop
);
icalproperty
*
etzid
=
icalproperty_new_equivalenttzid
(
equiv
);
icalcomponent_add_property
(
vtz
,
etzid
);
/* Substitute TZID alias */
icalproperty_set_tzid
(
prop
,
tzid
);
}
/* Start constructing TZURL */
buf_reset
(
&
pathbuf
);
http_proto_host
(
txn
->
req_hdrs
,
&
proto
,
&
host
);
buf_printf
(
&
pathbuf
,
"%s://%s%s?action=get&tzid=%s"
,
proto
,
host
,
namespace_timezone
.
prefix
,
tzid
);
if
(
mime
!=
tz_mime_types
)
{
buf_printf
(
&
pathbuf
,
"&format=%.*s"
,
(
int
)
strcspn
(
mime
->
content_type
,
";"
),
mime
->
content_type
);
}
if
(
truncate
)
{
if
(
!
icaltime_is_null_time
(
end
))
{
/* Add TZUNTIL to VTIMEZONE */
icalproperty
*
tzuntil
=
icalproperty_new_tzuntil
(
end
);
icalcomponent_add_property
(
vtz
,
tzuntil
);
}
/* Add truncation parameter to TZURL */
buf_printf
(
&
pathbuf
,
"&truncate=%s"
,
truncate
);
/* Truncate the VTIMEZONE */
truncate_vtimezone
(
vtz
,
start
,
end
,
NULL
);
}
/* Set TZURL property */
prop
=
icalproperty_new_tzurl
(
buf_cstring
(
&
pathbuf
));
icalcomponent_add_property
(
vtz
,
prop
);
/* Convert to requested MIME type */
data
=
mime
->
to_string
(
ical
);
datalen
=
strlen
(
data
);
/* Set Content-Disposition filename */
buf_reset
(
&
pathbuf
);
buf_printf
(
&
pathbuf
,
"%s.%s"
,
tzid
,
mime
->
file_ext
);
resp_body
->
fname
=
buf_cstring
(
&
pathbuf
);
icalcomponent_free
(
ical
);
}
write_body
(
precond
,
txn
,
data
,
datalen
);
if
(
data
)
free
(
data
);
return
0
;
}
static
int
observance_compare
(
const
void
*
obs1
,
const
void
*
obs2
)
{
return
icaltime_compare
(((
struct
observance
*
)
obs1
)
->
onset
,
((
struct
observance
*
)
obs2
)
->
onset
);
}
static
const
char
*
dow
[]
=
{
"Sun"
,
"Mon"
,
"Tue"
,
"Wed"
,
"Thu"
,
"Fri"
,
"Sat"
};
static
const
char
*
mon
[]
=
{
"Jan"
,
"Feb"
,
"Mar"
,
"Apr"
,
"May"
,
"Jun"
,
"Jul"
,
"Aug"
,
"Sep"
,
"Oct"
,
"Nov"
,
"Dec"
};
#define CTIME_FMT "%s %s %2d %02d:%02d:%02d %4d"
#define CTIME_ARGS(tt) \
dow[icaltime_day_of_week(tt)-1], mon[tt.month-1], \
tt.day, tt.hour, tt.minute, tt.second, tt.year
/* Perform an expand action */
static
int
action_expand
(
struct
transaction_t
*
txn
)
{
int
r
,
precond
,
zdump
=
0
;
struct
strlist
*
param
;
const
char
*
tzid
;
struct
zoneinfo
zi
;
time_t
lastmod
;
icaltimetype
start
,
end
,
changedsince
=
icaltime_null_time
();
struct
resp_body_t
*
resp_body
=
&
txn
->
resp_body
;
json_t
*
root
=
NULL
;
/* Sanity check the parameters */
param
=
hash_lookup
(
"tzid"
,
&
txn
->
req_qparams
);
if
(
!
param
||
param
->
next
)
{
/* mandatory, once only */
return
json_error_response
(
txn
,
TZ_INVALID_TZID
,
param
,
NULL
);
}
if
(
strchr
(
param
->
s
,
'.'
))
{
/* paranoia */
return
json_error_response
(
txn
,
TZ_NOT_FOUND
,
NULL
,
NULL
);
}
tzid
=
param
->
s
;
param
=
hash_lookup
(
"changedsince"
,
&
txn
->
req_qparams
);
if
(
param
)
{
changedsince
=
icaltime_from_string
(
param
->
s
);
if
(
param
->
next
||
!
changedsince
.
is_utc
)
{
/* once only, UTC */
return
json_error_response
(
txn
,
TZ_INVALID_CHANGEDSINCE
,
param
,
&
changedsince
);
}
}
param
=
hash_lookup
(
"start"
,
&
txn
->
req_qparams
);
if
(
!
param
||
param
->
next
)
/* mandatory, once only */
return
json_error_response
(
txn
,
TZ_INVALID_START
,
param
,
NULL
);
start
=
icaltime_from_string
(
param
->
s
);
if
(
!
start
.
is_utc
)
/* MUST be UTC */
return
json_error_response
(
txn
,
TZ_INVALID_START
,
param
,
&
start
);
param
=
hash_lookup
(
"end"
,
&
txn
->
req_qparams
);
if
(
param
)
{
end
=
icaltime_from_string
(
param
->
s
);
if
(
param
->
next
||
!
end
.
is_utc
/* once only, UTC */
||
icaltime_compare
(
end
,
start
)
<=
0
)
{
/* end MUST be > start */
return
json_error_response
(
txn
,
TZ_INVALID_END
,
param
,
&
end
);
}
}
else
{
/* Default to start + 10 years */
end
=
start
;
end
.
year
+=
10
;
}
/* Check requested format (debugging only) */
param
=
hash_lookup
(
"format"
,
&
txn
->
req_qparams
);
if
(
param
)
{
if
(
param
->
next
||
strcmp
(
param
->
s
,
"zdump"
))
/* optional, once only */
return
json_error_response
(
txn
,
TZ_INVALID_FORMAT
,
param
,
NULL
);
/* Mimic zdump(8) -V output for comparision:
Print the times both one second before and exactly at each
detected time discontinuity. Each line ends with isdst=1
if the given time is Daylight Saving Time or isdst=0 otherwise.
*/
zdump
=
1
;
}
/* Get info record from the database */
if
((
r
=
zoneinfo_lookup
(
tzid
,
&
zi
)))
{
return
(
r
==
CYRUSDB_NOTFOUND
?
json_error_response
(
txn
,
TZ_NOT_FOUND
,
NULL
,
NULL
)
:
HTTP_SERVER_ERROR
);
}
/* Generate ETag & Last-Modified from info record */
assert
(
!
buf_len
(
&
txn
->
buf
));
buf_printf
(
&
txn
->
buf
,
"%u-%ld"
,
strhash
(
tzid
),
zi
.
dtstamp
);
lastmod
=
zi
.
dtstamp
;
freestrlist
(
zi
.
data
);
/* Check any preconditions, including range request */
txn
->
flags
.
ranges
=
1
;
if
(
lastmod
<=
icaltime_as_timet
(
changedsince
))
precond
=
HTTP_NOT_MODIFIED
;
else
precond
=
check_precond
(
txn
,
NULL
,
buf_cstring
(
&
txn
->
buf
),
lastmod
);
switch
(
precond
)
{
case
HTTP_OK
:
case
HTTP_PARTIAL
:
case
HTTP_NOT_MODIFIED
:
/* Fill in ETag, Last-Modified, and Expires */
resp_body
->
etag
=
buf_cstring
(
&
txn
->
buf
);
resp_body
->
lastmod
=
lastmod
;
resp_body
->
maxage
=
86400
;
/* 24 hrs */
txn
->
flags
.
cc
|=
CC_MAXAGE
|
CC_REVALIDATE
;
if
(
httpd_userid
)
txn
->
flags
.
cc
|=
CC_PUBLIC
;
if
(
precond
!=
HTTP_NOT_MODIFIED
)
break
;
default
:
/* We failed a precondition - don't perform the request */
resp_body
->
type
=
NULL
;
return
precond
;
}
if
(
txn
->
meth
!=
METH_HEAD
)
{
static
struct
buf
pathbuf
=
BUF_INITIALIZER
;
const
char
*
path
,
*
msg_base
=
NULL
;
unsigned
long
msg_size
=
0
;
icalcomponent
*
ical
,
*
vtz
;
const
struct
observance
*
proleptic
;
char
dtstamp
[
21
];
icalarray
*
obsarray
;
json_t
*
jobsarray
;
unsigned
n
;
int
fd
;
/* Open, mmap, and parse the file */
buf_reset
(
&
pathbuf
);
buf_printf
(
&
pathbuf
,
"%s%s/%s.ics"
,
config_dir
,
FNAME_ZONEINFODIR
,
tzid
);
path
=
buf_cstring
(
&
pathbuf
);
if
((
fd
=
open
(
path
,
O_RDONLY
))
==
-1
)
return
HTTP_SERVER_ERROR
;
map_refresh
(
fd
,
1
,
&
msg_base
,
&
msg_size
,
MAP_UNKNOWN_LEN
,
path
,
NULL
);
if
(
!
msg_base
)
return
HTTP_SERVER_ERROR
;
ical
=
icalparser_parse_string
(
msg_base
);
map_free
(
&
msg_base
,
&
msg_size
);
close
(
fd
);
/* Start constructing our response */
if
(
zdump
)
{
txn
->
resp_body
.
type
=
"text/plain; charset=us-ascii"
;
}
else
{
rfc3339date_gen
(
dtstamp
,
sizeof
(
dtstamp
),
lastmod
);
root
=
json_pack
(
"{s:s s:s s:[]}"
,
"dtstamp"
,
dtstamp
,
"tzid"
,
tzid
,
"observances"
);
if
(
!
root
)
{
txn
->
error
.
desc
=
"Unable to create JSON response"
;
return
HTTP_SERVER_ERROR
;
}
}
/* Create an array of observances */
obsarray
=
icalarray_new
(
sizeof
(
struct
observance
),
20
);
vtz
=
icalcomponent_get_first_component
(
ical
,
ICAL_VTIMEZONE_COMPONENT
);
proleptic
=
truncate_vtimezone
(
vtz
,
start
,
end
,
obsarray
);
/* Sort the observances by onset */
icalarray_sort
(
obsarray
,
&
observance_compare
);
if
(
zdump
)
{
struct
buf
*
body
=
&
txn
->
resp_body
.
payload
;
struct
icaldurationtype
off
=
icaldurationtype_null_duration
();
const
char
*
prev_name
=
proleptic
->
name
;
int
prev_isdst
=
proleptic
->
is_daylight
;
for
(
n
=
0
;
n
<
obsarray
->
num_elements
;
n
++
)
{
struct
observance
*
obs
=
icalarray_element_at
(
obsarray
,
n
);
struct
icaltimetype
local
,
ut
;
/* Skip any no-ops as zdump doesn't output them */
if
(
obs
->
offset_from
==
obs
->
offset_to
&&
prev_isdst
==
obs
->
is_daylight
&&
!
strcmp
(
prev_name
,
obs
->
name
))
continue
;
/* UT and local time 1 second before onset */
off
.
seconds
=
-1
;
ut
=
icaltime_add
(
obs
->
onset
,
off
);
off
.
seconds
=
obs
->
offset_from
;
local
=
icaltime_add
(
ut
,
off
);
buf_printf
(
body
,
"%s "
CTIME_FMT
" UT = "
CTIME_FMT
" %s"
" isdst=%d gmtoff=%d
\n
"
,
tzid
,
CTIME_ARGS
(
ut
),
CTIME_ARGS
(
local
),
prev_name
,
prev_isdst
,
obs
->
offset_from
);
/* UT and local time at onset */
icaltime_adjust
(
&
ut
,
0
,
0
,
0
,
1
);
off
.
seconds
=
obs
->
offset_to
;
local
=
icaltime_add
(
ut
,
off
);
buf_printf
(
body
,
"%s "
CTIME_FMT
" UT = "
CTIME_FMT
" %s"
" isdst=%d gmtoff=%d
\n
"
,
tzid
,
CTIME_ARGS
(
ut
),
CTIME_ARGS
(
local
),
obs
->
name
,
obs
->
is_daylight
,
obs
->
offset_to
);
prev_name
=
obs
->
name
;
prev_isdst
=
obs
->
is_daylight
;
}
}
else
{
/* Add observances to JSON array */
jobsarray
=
json_object_get
(
root
,
"observances"
);
for
(
n
=
0
;
n
<
obsarray
->
num_elements
;
n
++
)
{
struct
observance
*
obs
=
icalarray_element_at
(
obsarray
,
n
);
json_array_append_new
(
jobsarray
,
json_pack
(
"{s:s s:s s:i s:i}"
,
"name"
,
obs
->
name
,
"onset"
,
icaltime_as_iso_string
(
obs
->
onset
),
"utc-offset-from"
,
obs
->
offset_from
,
"utc-offset-to"
,
obs
->
offset_to
));
}
}
icalarray_free
(
obsarray
);
icalcomponent_free
(
ical
);
}
if
(
zdump
)
{
struct
buf
*
body
=
&
txn
->
resp_body
.
payload
;
write_body
(
precond
,
txn
,
buf_cstring
(
body
),
buf_len
(
body
));
return
0
;
}
else
{
/* Output the JSON object */
return
json_response
(
precond
,
txn
,
root
,
NULL
);
}
}
static
int
json_response
(
int
code
,
struct
transaction_t
*
txn
,
json_t
*
root
,
char
**
resp
)
{
size_t
flags
=
JSON_PRESERVE_ORDER
;
char
*
buf
=
NULL
;
if
(
root
)
{
/* Dump JSON object into a text buffer */
flags
|=
(
config_httpprettytelemetry
?
JSON_INDENT
(
2
)
:
JSON_COMPACT
);
buf
=
json_dumps
(
root
,
flags
);
json_decref
(
root
);
if
(
!
buf
)
{
txn
->
error
.
desc
=
"Error dumping JSON object"
;
return
HTTP_SERVER_ERROR
;
}
else
if
(
resp
)
{
if
(
*
resp
)
free
(
*
resp
);
*
resp
=
buf
;
}
}
else
if
(
resp
)
buf
=
*
resp
;
/* Output the JSON object */
txn
->
resp_body
.
type
=
"application/json; charset=utf-8"
;
write_body
(
code
,
txn
,
buf
,
buf
?
strlen
(
buf
)
:
0
);
if
(
!
resp
&&
buf
)
free
(
buf
);
return
0
;
}
/* Array of parameter names - MUST be kept in sync with tz_err.et */
static
const
char
*
param_names
[]
=
{
"action"
,
"tzid"
,
"pattern"
,
"format"
,
"start"
,
"end"
,
"changedsince"
,
"truncate"
,
"tzid"
};
static
int
json_error_response
(
struct
transaction_t
*
txn
,
long
tz_code
,
struct
strlist
*
param
,
icaltimetype
*
time
)
{
long
http_code
=
HTTP_BAD_REQUEST
;
const
char
*
param_name
,
*
fmt
=
NULL
;
char
desc
[
100
];
json_t
*
root
;
param_name
=
param_names
[
tz_code
-
tz_err_base
];
if
(
!
param
)
fmt
=
"missing %s parameter"
;
else
if
(
param
->
next
)
fmt
=
"multiple %s parameters"
;
else
if
(
!
param
->
s
||
!
param
->
s
[
0
])
fmt
=
"missing %s value"
;
else
if
(
!
time
)
fmt
=
"unknown %s value"
;
else
if
(
!
time
->
is_utc
)
fmt
=
"invalid %s UTC value"
;
switch
(
tz_code
)
{
case
TZ_INVALID_TZID
:
if
(
!
fmt
)
fmt
=
"tzid used with changedsince"
;
break
;
case
TZ_INVALID_END
:
case
TZ_INVALID_TRUNCATE
:
if
(
!
fmt
)
fmt
=
"end <= start"
;
break
;
case
TZ_NOT_FOUND
:
http_code
=
HTTP_NOT_FOUND
;
fmt
=
"time zone not found"
;
break
;
}
snprintf
(
desc
,
sizeof
(
desc
),
fmt
?
fmt
:
"unknown error"
,
param_name
);
root
=
json_pack
(
"{s:s s:s}"
,
"error"
,
error_message
(
tz_code
),
"description"
,
desc
);
if
(
!
root
)
{
txn
->
error
.
desc
=
"Unable to create JSON response"
;
return
HTTP_SERVER_ERROR
;
}
return
json_response
(
http_code
,
txn
,
root
,
NULL
);
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Fri, Apr 24, 9:47 AM (1 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18857671
Default Alt Text
http_timezone.c (41 KB)
Attached To
Mode
R111 cyrus-imapd
Attached
Detach File
Event Timeline