diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..b27c449 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,3 @@ + +(C) 2015 Graham Leggett + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..4909afd --- /dev/null +++ b/COPYING @@ -0,0 +1,178 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..d9ca5a5 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,5 @@ + +Changes with v0.0.1 + + *) Initial packaging. [Graham Leggett ] + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..2099840 --- /dev/null +++ b/INSTALL @@ -0,0 +1,370 @@ +Installation Instructions +************************* + +Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation, +Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell command `./configure && make && make install' +should configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. Some packages provide this +`INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + + The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type `make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the `make install' phase executed with root + privileges. + + 5. Optionally, type `make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior `make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type `make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide `make + distcheck', which can by used by developers to test that all other + targets like `make install' and `make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. This +is known as a "VPATH" build. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the +default for these options is expressed in terms of `${prefix}', so that +specifying just `--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to `configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +`make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, `make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +`${prefix}'. Any directories that were specified during `configure', +but not in terms of `${prefix}', must each be overridden at install +time for the entire installation to be relocated. The approach of +makefile variable overrides for each directory variable is required by +the GNU Coding Standards, and ideally causes no recompilation. +However, some platforms have known limitations with the semantics of +shared libraries that end up requiring recompilation when using this +method, particularly noticeable in packages that use GNU Libtool. + + The second method involves providing the `DESTDIR' variable. For +example, `make install DESTDIR=/alternate/directory' will prepend +`/alternate/directory' before all installation names. The approach of +`DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of `${prefix}' +at `configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of `make' will be. For these packages, running `./configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with `make V=1'; while running `./configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with `make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + HP-UX `make' updates targets which have the same time stamps as +their prerequisites, which makes it generally unusable when shipped +generated files such as `configure' are involved. Use GNU `make' +instead. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put `/usr/ucb' early in your `PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in `/usr/bin'. So, if you need `/usr/ucb' +in your `PATH', put it _after_ `/usr/bin'. + + On Haiku, software installed for all users goes in `/boot/common', +not `/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf limitation. Until the limitation is lifted, you can use +this workaround: + + CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..c04a611 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,11 @@ + + +EXTRA_DIST = mod_ical.c mod_ical.spec debian/changelog debian/compat debian/control debian/copyright debian/docs debian/mod-ical.substvars debian/mod-ical.dirs debian/rules debian/source/format + +all-local: + $(APXS) -c $(DEF_LDLIBS) -Wc,"$(CFLAGS)" -Wc,"$(AM_CFLAGS)" -Wl,"$(LDFLAGS)" -Wl,"$(AM_LDFLAGS)" $(LIBS) @srcdir@/mod_ical.c + +install-exec-local: + mkdir -p $(DESTDIR)`$(APXS) -q LIBEXECDIR` + $(APXS) -S LIBEXECDIR=$(DESTDIR)`$(APXS) -q LIBEXECDIR` -c -i $(DEF_LDLIBS) -Wc,"$(CFLAGS)" -Wc,"$(AM_CFLAGS)" -Wl,"$(LDFLAGS)" -Wl,"$(AM_LDFLAGS)" $(LIBS) @srcdir@/mod_ical.c + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..28cf578 --- /dev/null +++ b/configure.ac @@ -0,0 +1,58 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.61) +AC_INIT(mod_ical, 0.0.1, minfrin@sharp.fm) +AM_INIT_AUTOMAKE([dist-bzip2]) +AC_CONFIG_FILES([Makefile mod_ical.spec]) +AC_CONFIG_SRCDIR([mod_ical.c]) + +# Checks for programs. +AC_PROG_CC +AC_ARG_WITH(apxs, + [ --with-apxs=PATH path to Apache apxs], + [ + if test "$withval" = "yes"; then + AC_PATH_PROGS(APXS, apxs apxs2, reject, $PATH:/usr/sbin) + else + APXS=$withval + AC_SUBST(APXS) + fi + ], + [ + AC_PATH_PROGS(APXS, apxs apxs2, reject, $PATH:/usr/sbin) + ]) +if test "$APXS" = "reject"; then + AC_MSG_ERROR([Could not find apxs on the path.]) +fi + +# Make sure the Apache include files are found +CPPFLAGS="$CPPFLAGS -I`$APXS -q INCLUDEDIR`" +CFLAGS="$CFLAGS -I`$APXS -q INCLUDEDIR`" + +# Checks for libraries. +PKG_CHECK_MODULES(apr, apr-1 >= 1.2) +PKG_CHECK_MODULES(apu, apr-util-1 >= 1.2) +PKG_CHECK_MODULES(libical, libical >= 0.40) +PKG_CHECK_MODULES(libxml, libxml-2.0 > 2) +PKG_CHECK_MODULES(jsonc, json-c > 0.10) + +CFLAGS="$CFLAGS $apr_CFLAGS $apu_CFLAGS $libical_CFLAGS $libxml_CFLAGS $jsonc_CFLAGS" +CPPFLAGS="$CPPFLAGS $apr_CPPFLAGS $apu_CPPFLAGS $libical_CPPFLAGS $libxml_CPPFLAGS $jsonc_CPPFLAGS" +LDFLAGS="$LDFLAGS $apr_LIBS $apu_LIBS $libical_LIBS $libxml_LIBS $jsonc_LIBS" + +# Checks for header files. +AC_CHECK_HEADER(libical/ical.h) +AC_CHECK_HEADER(libxml/encoding.h) +AC_CHECK_HEADER(json-c/json.h) + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_SIZE_T + +# Checks for library functions. +AC_CHECK_LIB(ical, icalparser_new) +AC_CHECK_LIB(xml2, htmlParseDocument) +AC_CHECK_LIB(json-c, json_object_new_object) + +AC_SUBST(PACKAGE_VERSION) +AC_OUTPUT diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..5f4510b --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +mod-ical (0.0.1) stable; urgency=low + + * Initial Release. + + -- Graham Leggett Sun, 11 Nov 2012 13:37:46 +0000 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..45a4fb7 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +8 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..47c7c5d --- /dev/null +++ b/debian/control @@ -0,0 +1,13 @@ +Source: mod-ical +Priority: extra +Maintainer: Graham Leggett +Build-Depends: debhelper (>= 8.0.0), autotools-dev, apache2-dev, libical-dev, libjson-c-dev, libxml2-dev +Standards-Version: 3.9.2 +Section: libs + +Package: mod-ical +Section: libs +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, libical, libjson-c, libxml2 +Description: The Apache mod_ical module provides a set of filters to filter iCalendar data and convert it to xCal/jCal. + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..059921f --- /dev/null +++ b/debian/copyright @@ -0,0 +1,26 @@ +Format: http://dep.debian.net/deps/dep5 +Upstream-Name: mod-ical +Source: http://source/mod_ical/ + +Files: * +Copyright: 2015 Graham Leggett +License: Apache-2.0 + +License: Apache-2.0 + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + . + http://www.apache.org/licenses/LICENSE-2.0 + . + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + . + On Debian systems, the complete text of the Apache version 2.0 license + can be found in "/usr/share/common-licenses/Apache-2.0". + +# Please also look if there are files or directories which have a +# different copyright/license attached and list them here. diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..50bd824 --- /dev/null +++ b/debian/docs @@ -0,0 +1,2 @@ +NEWS +README diff --git a/debian/mod-ical.dirs b/debian/mod-ical.dirs new file mode 100644 index 0000000..6845771 --- /dev/null +++ b/debian/mod-ical.dirs @@ -0,0 +1 @@ +usr/lib diff --git a/debian/mod-ical.substvars b/debian/mod-ical.substvars new file mode 100644 index 0000000..abd3ebe --- /dev/null +++ b/debian/mod-ical.substvars @@ -0,0 +1 @@ +misc:Depends= diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..b760bee --- /dev/null +++ b/debian/rules @@ -0,0 +1,13 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +%: + dh $@ diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/mod_ical.c b/mod_ical.c new file mode 100644 index 0000000..a591aca --- /dev/null +++ b/mod_ical.c @@ -0,0 +1,2411 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_ical.c: iCalendar filters + * + * ical2xcal: Convert an iCalendar stream into an XML stream (rfc6321) + * + * icalfilter: Show next / last calendar item based on today's date. + * + * Convert iCalendar into the local timezone of the client? + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "http_protocol.h" +#include "util_filter.h" +#include "apr_strings.h" +#include "apr_lib.h" + +#include +#include +#include + +#include +#include + +#include + +#include + +module AP_MODULE_DECLARE_DATA ical_module; + + +#define DEFAULT_ICAL_FILTER AP_ICAL_FILTER_NEXT +#define DEFAULT_ICAL_FORMAT AP_ICAL_FORMAT_NONE + +#define XCAL_HEADER "" \ + "" +#define XCAL_FOOTER "" + +typedef enum { + AP_ICAL_FILTER_NONE, + AP_ICAL_FILTER_NEXT, + AP_ICAL_FILTER_LAST, + AP_ICAL_FILTER_FUTURE, + AP_ICAL_FILTER_PAST, + AP_ICAL_FILTER_UNKNOWN +} ap_ical_filter_e; + +typedef enum { + AP_ICAL_FORMAT_NONE, + AP_ICAL_FORMAT_SPACED, + AP_ICAL_FORMAT_PRETTY, + AP_ICAL_FORMAT_UNKNOWN +} ap_ical_format_e; + +typedef enum { + AP_ICAL_OUTPUT_NEGOTIATED, + AP_ICAL_OUTPUT_ICAL, + AP_ICAL_OUTPUT_XCAL, + AP_ICAL_OUTPUT_JCAL +} ap_ical_output_e; + +typedef struct ical_ctx { + apr_bucket_brigade *bb; + apr_bucket_brigade *tmp; + icalparser *parser; + int seen_eol; + int eat_crlf; + int seen_eos; + ap_ical_output_e output; + ap_ical_filter_e filter; + ap_ical_format_e format; +} ical_ctx; + +typedef struct ical_conf { + unsigned int filter_set:1; /* has filtering been set */ + unsigned int format_set:1; /* has formatting been set */ + ap_ical_filter_e filter; /* type of filtering */ + ap_ical_format_e format; /* type of formatting */ +} ical_conf; + +static apr_status_t icalparser_cleanup(void *data) +{ + icalparser *parser = data; + icalparser_free(parser); + return APR_SUCCESS; +} + +static apr_status_t icalcomponent_cleanup(void *data) +{ + icalcomponent *comp = data; + icalcomponent_free(comp); + return APR_SUCCESS; +} + +static apr_status_t jsonbuffer_cleanup(void *data) +{ + json_object *buf = data; + json_object_put(buf); + return APR_SUCCESS; +} + +static apr_status_t xmlbuffer_cleanup(void *data) +{ + xmlBufferPtr buf = data; + xmlBufferFree(buf); + return APR_SUCCESS; +} + +static apr_status_t xmlwriter_cleanup(void *data) +{ + xmlTextWriterPtr writer = data; + xmlFreeTextWriter(writer); + return APR_SUCCESS; +} + +static char *strlwr(char *str) +{ + apr_size_t i; + apr_size_t len = strlen(str); + + for (i = 0; i < len; i++) + str[i] = apr_tolower((unsigned char) str[i]); + + return str; +} + +static const char *icalrecur_weekday_to_string(icalrecurrencetype_weekday kind) +{ + switch (kind) { + case ICAL_SUNDAY_WEEKDAY: { + return "SU"; + } + case ICAL_MONDAY_WEEKDAY: { + return "MO"; + } + case ICAL_TUESDAY_WEEKDAY: { + return "TU"; + } + case ICAL_WEDNESDAY_WEEKDAY: { + return "WE"; + } + case ICAL_THURSDAY_WEEKDAY: { + return "TH"; + } + case ICAL_FRIDAY_WEEKDAY: { + return "FR"; + } + case ICAL_SATURDAY_WEEKDAY: { + return "SA"; + } + default: { + return "UNKNOWN"; + } + } +} + +#define ICAL_LEAP_MONTH 0x1000 + +static int icalrecurrencetype_month_is_leap(short month) +{ + return (month & ICAL_LEAP_MONTH); +} + +static int icalrecurrencetype_month_month(short month) +{ + return (month & ~ICAL_LEAP_MONTH); +} + +static apr_status_t icalduration_to_json(const char *element, + struct icaldurationtype duration, json_object *jarray) +{ + apr_status_t rv = APR_SUCCESS; + + /* write duration element */ + { + char *str = icaldurationtype_as_ical_string_r(duration); + json_object_array_add(jarray, json_object_new_string(str)); + icalmemory_free_buffer(str); + } + + return rv; +} + +static apr_status_t icalduration_to_xml(const char *element, + struct icaldurationtype duration, xmlTextWriterPtr writer) +{ + apr_status_t rv = APR_SUCCESS; + int rc; + + /* open duration element */ + rc = xmlTextWriterStartElement(writer, BAD_CAST element); + if (rc < 0) { + return APR_EGENERAL; + } + + /* write duration element */ + { + char *str = icaldurationtype_as_ical_string_r(duration); + rc = xmlTextWriterWriteFormatRaw(writer, "%s", str); + icalmemory_free_buffer(str); + } + + /* close duration element */ + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + return rv; +} + +static apr_status_t icaltime_to_json(ap_filter_t *f, const char *element, + struct icaltimetype tt, json_object *jarray) +{ + apr_status_t rv = APR_SUCCESS; + + if (tt.is_date) { + json_object_array_add(jarray, + json_object_new_string( + apr_psprintf(f->r->pool, "%04d-%02d-%02d", tt.year, + tt.month, tt.day))); + } + else { + json_object_array_add(jarray, + json_object_new_string( + apr_psprintf(f->r->pool, + "%04d-%02d-%02dT%02d:%02d:%02d", tt.year, + tt.month, tt.day, tt.hour, tt.minute, + tt.second))); + } + + return rv; +} + +static apr_status_t icaltime_to_xml(const char *element, struct icaltimetype tt, + xmlTextWriterPtr writer) +{ + apr_status_t rv = APR_SUCCESS; + int rc; + + /* open time element */ + rc = xmlTextWriterStartElement(writer, BAD_CAST element); + if (rc < 0) { + return APR_EGENERAL; + } + + if (tt.is_date) { + rc = xmlTextWriterWriteFormatRaw(writer, "%04d-%02d-%02d", tt.year, + tt.month, tt.day); + if (rc < 0) { + return APR_EGENERAL; + } + } + else { + rc = xmlTextWriterWriteFormatRaw(writer, + "%04d-%02d-%02dT%02d:%02d:%02d", tt.year, tt.month, tt.day, + tt.hour, tt.minute, tt.second); + if (rc < 0) { + return APR_EGENERAL; + } + } + + /* close property element */ + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + return rv; +} + +static apr_status_t icalrecurrence_byday_to_json(ap_filter_t *f, + const char *element, short *array, short limit, json_object *jobj) +{ + int i; + + if (array[0] != ICAL_RECURRENCE_ARRAY_MAX) { + + json_object *jarray = json_object_new_array(); + json_object_object_add(jobj, element, jarray); + + for (i = 0; i < limit && array[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) { + + int pos = icalrecurrencetype_day_position(array[i]); + int dow = icalrecurrencetype_day_day_of_week(array[i]); + const char *daystr = icalrecur_weekday_to_string(dow); + + if (pos == 0) { + + json_object_array_add(jarray, + json_object_new_string( + apr_psprintf(f->r->pool, "%s", daystr))); + + } + else { + + json_object_array_add(jarray, + json_object_new_string( + apr_psprintf(f->r->pool, "%d%s", pos, daystr))); + + } + + } + + } + + return APR_SUCCESS; +} + +static apr_status_t icalrecurrence_byday_to_xml(const char *element, + short *array, short limit, xmlTextWriterPtr writer) +{ + int i, rc; + + for (i = 0; i < limit && array[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) { + + int pos = icalrecurrencetype_day_position(array[i]); + int dow = icalrecurrencetype_day_day_of_week(array[i]); + const char *daystr = icalrecur_weekday_to_string(dow); + + rc = xmlTextWriterStartElement(writer, BAD_CAST element); + if (rc < 0) { + return APR_EGENERAL; + } + + if (pos == 0) { + rc = xmlTextWriterWriteFormatRaw(writer, "%s", daystr); + } + else { + rc = xmlTextWriterWriteFormatRaw(writer, "%d%s", pos, daystr); + } + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + return APR_SUCCESS; +} + +static apr_status_t icalrecurrence_bymonth_to_json(ap_filter_t *f, + const char *element, short *array, short limit, json_object *jobj) +{ + int i; + + if (array[0] != ICAL_RECURRENCE_ARRAY_MAX) { + + json_object *jarray = json_object_new_array(); + json_object_object_add(jobj, element, jarray); + + for (i = 0; i < limit && array[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) { + + /* rfc7529 introduces the leap month */ + if (icalrecurrencetype_month_is_leap(array[i])) { + + json_object_array_add(jarray, + json_object_new_string( + apr_psprintf(f->r->pool, "%dL", + icalrecurrencetype_month_month( + array[i])))); + + } + else { + + json_object_array_add(jarray, + json_object_new_int( + (int32_t) icalrecurrencetype_month_month( + array[i]))); + + } + + } + + } + + return APR_SUCCESS; +} + +static apr_status_t icalrecurrence_bymonth_to_xml(const char *element, + short *array, short limit, xmlTextWriterPtr writer) +{ + int i, rc; + + for (i = 0; i < limit && array[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) { + + rc = xmlTextWriterStartElement(writer, BAD_CAST element); + if (rc < 0) { + return APR_EGENERAL; + } + + if (icalrecurrencetype_month_is_leap(array[i])) { + rc = xmlTextWriterWriteFormatRaw(writer, "%dL", + icalrecurrencetype_month_month(array[i])); + } + else { + rc = xmlTextWriterWriteFormatRaw(writer, "%d", array[i]); + } + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + return APR_SUCCESS; +} + +static apr_status_t icalrecurrence_by_to_json(const char *element, short *array, + short limit, json_object *jobj) +{ + int i; + + if (array[0] != ICAL_RECURRENCE_ARRAY_MAX) { + + json_object *jarray = json_object_new_array(); + json_object_object_add(jobj, element, jarray); + + for (i = 0; i < limit && array[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) { + + json_object_array_add(jarray, + json_object_new_int((int32_t) array[i])); + + } + + } + + return APR_SUCCESS; +} + +static apr_status_t icalrecurrence_by_to_xml(const char *element, short *array, + short limit, xmlTextWriterPtr writer) +{ + int i, rc; + + for (i = 0; i < limit && array[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) { + + rc = xmlTextWriterStartElement(writer, BAD_CAST element); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterWriteFormatRaw(writer, "%d", array[i]); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + return APR_SUCCESS; +} + +static apr_status_t icalrecurrencetype_to_json(ap_filter_t *f, + struct icalrecurrencetype *recur, json_object *jarray) +{ + apr_status_t rv = APR_SUCCESS; + int rc; + + json_object *jobj = json_object_new_object(); + json_object_array_add(jarray, jobj); + + if (recur->freq != ICAL_NO_RECURRENCE) { + + if (recur->until.year != 0) { + + if (recur->until.is_date) { + json_object_object_add(jobj, "until", + json_object_new_string( + apr_psprintf(f->r->pool, "%04d-%02d-%02d", + recur->until.year, recur->until.month, + recur->until.day))); + } + else { + json_object_object_add(jobj, "until", + json_object_new_string( + apr_psprintf(f->r->pool, + "%04d-%02d-%02dT%02d:%02d:%02d", + recur->until.year, recur->until.month, + recur->until.day, recur->until.hour, + recur->until.minute, + recur->until.second))); + } + + } + + if (recur->count != 0) { + + json_object_object_add(jobj, "count", + json_object_new_int((int32_t) recur->count)); + + } + + if (recur->interval != 1) { + + json_object_object_add(jobj, "interval", + json_object_new_int((int32_t) recur->interval)); + + } + + rc = icalrecurrence_by_to_json("bysecond", recur->by_second, + ICAL_BY_SECOND_SIZE, jobj); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_by_to_json("byminute", recur->by_minute, + ICAL_BY_MINUTE_SIZE, jobj); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_by_to_json("byhour", recur->by_hour, + ICAL_BY_HOUR_SIZE, jobj); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_byday_to_json(f, "byday", recur->by_day, ICAL_BY_DAY_SIZE, + jobj); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_by_to_json("bymonthday", recur->by_month_day, + ICAL_BY_MONTHDAY_SIZE, jobj); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_by_to_json("byyearday", recur->by_year_day, + ICAL_BY_YEARDAY_SIZE, jobj); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_by_to_json("byweekno", recur->by_week_no, + ICAL_BY_WEEKNO_SIZE, jobj); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_bymonth_to_json(f, "bymonth", recur->by_month, + ICAL_BY_MONTH_SIZE, jobj); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_by_to_json("bysetpos", recur->by_set_pos, + ICAL_BY_SETPOS_SIZE, jobj); + if (rc < 0) { + return APR_EGENERAL; + } + + /* Monday is the default, so no need to write that out */ + if (recur->week_start != ICAL_MONDAY_WEEKDAY + && recur->week_start != ICAL_NO_WEEKDAY) { + + int dow = icalrecurrencetype_day_day_of_week(recur->week_start); + + json_object_object_add(jobj, "wkst", + json_object_new_string( + apr_psprintf(f->r->pool, "%s", + icalrecur_weekday_to_string(dow)))); + + } + + } + + return rv; +} + +static apr_status_t icalrecurrencetype_to_xml(ap_filter_t *f, + struct icalrecurrencetype *recur, xmlTextWriterPtr writer) +{ + apr_status_t rv = APR_SUCCESS; + int rc; + + if (recur->freq != ICAL_NO_RECURRENCE) { + + if (recur->until.year != 0) { + + rc = xmlTextWriterStartElement(writer, BAD_CAST "until"); + if (rc < 0) { + return APR_EGENERAL; + } + + if (recur->until.is_date) { + rc = xmlTextWriterWriteFormatRaw(writer, + "%04d-%02d-%02d", recur->until.year, + recur->until.month, recur->until.day); + } + else { + rc = xmlTextWriterWriteFormatRaw(writer, + "%04d-%02d-%02dT%02d:%02d:%02d", + recur->until.year, recur->until.month, + recur->until.day, recur->until.hour, + recur->until.minute, recur->until.second); + } + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + if (recur->count != 0) { + + rc = xmlTextWriterStartElement(writer, BAD_CAST "count"); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterWriteFormatRaw(writer, "%d", recur->count); + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + if (recur->interval != 1) { + + rc = xmlTextWriterStartElement(writer, BAD_CAST "interval"); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterWriteFormatRaw(writer, "%d", recur->interval); + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + rc = icalrecurrence_by_to_xml("bysecond", recur->by_second, + ICAL_BY_SECOND_SIZE, writer); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_by_to_xml("byminute", recur->by_minute, + ICAL_BY_MINUTE_SIZE, writer); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_by_to_xml("byhour", recur->by_hour, + ICAL_BY_HOUR_SIZE, writer); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_byday_to_xml("byday", recur->by_day, ICAL_BY_DAY_SIZE, + writer); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_by_to_xml("bymonthday", recur->by_month_day, + ICAL_BY_MONTHDAY_SIZE, writer); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_by_to_xml("byyearday", recur->by_year_day, + ICAL_BY_YEARDAY_SIZE, writer); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_by_to_xml("byweekno", recur->by_week_no, + ICAL_BY_WEEKNO_SIZE, writer); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_bymonth_to_xml("bymonth", recur->by_month, + ICAL_BY_MONTH_SIZE, writer); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = icalrecurrence_by_to_xml("bysetpos", recur->by_set_pos, + ICAL_BY_SETPOS_SIZE, writer); + if (rc < 0) { + return APR_EGENERAL; + } + + /* Monday is the default, so no need to write that out */ + if (recur->week_start != ICAL_MONDAY_WEEKDAY && + recur->week_start != ICAL_NO_WEEKDAY) { + + int dow = icalrecurrencetype_day_day_of_week( + recur->week_start); + + rc = xmlTextWriterStartElement(writer, BAD_CAST "wkst"); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterWriteFormatRaw(writer, "%s", + icalrecur_weekday_to_string(dow)); + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + } + + return rv; +} + +static apr_status_t icalvalue_multi_to_json(ap_filter_t *f, icalvalue *val, + json_object *jarray) +{ + if (val) { + char *str = icalvalue_as_ical_string_r(val); + + if (str) { + const char *slider = str; + + while (slider) { + const char *token = slider; + slider = ap_strchr(slider, ','); + + if (slider) { + json_object_array_add(jarray, + json_object_new_string( + apr_psprintf(f->r->pool, "%.*s", + (int) (slider - token), token))); + slider++; + } + else { + json_object_array_add(jarray, + json_object_new_string(token)); + } + + } + + icalmemory_free_buffer(str); + } + + } + + return APR_SUCCESS; +} + +static apr_status_t icalvalue_multi_to_xml(ap_filter_t *f, icalvalue *val, + xmlTextWriterPtr writer) +{ + int rc = 0; + + if (val) { + char *str = icalvalue_as_ical_string_r(val); + + if (str) { + const char *slider = str; + + while (slider) { + const char *token = slider; + slider = ap_strchr(slider, ','); + + if (slider) { + rc = xmlTextWriterWriteFormatRaw(writer, "%.*s", + (int) (slider - token), token); + slider++; + } + else { + rc = xmlTextWriterWriteFormatRaw(writer, "%s", token); + } + + } + + icalmemory_free_buffer(str); + } + + if (rc < 0) { + return APR_EGENERAL; + } + + } + + return APR_SUCCESS; +} + +static apr_status_t icalvalue_to_json(ap_filter_t *f, icalvalue *val, + json_object *jarray) +{ + apr_status_t rv = APR_SUCCESS; + + if (val) { + + icalvalue_kind kind = icalvalue_isa(val); + char *element = NULL; + + /* work out the value type */ + if (kind != ICAL_X_VALUE) { + element = apr_pstrdup(f->r->pool, icalvalue_kind_to_string(kind)); + } + if (element) { + element = strlwr(element); + } + else { + element = "unknown"; + } + + /* handle each type */ + switch (kind) { + case ICAL_ACTION_VALUE: + case ICAL_ATTACH_VALUE: + case ICAL_BINARY_VALUE: + case ICAL_BOOLEAN_VALUE: + case ICAL_CALADDRESS_VALUE: + case ICAL_CARLEVEL_VALUE: + case ICAL_CLASS_VALUE: + case ICAL_CMD_VALUE: + case ICAL_FLOAT_VALUE: + case ICAL_INTEGER_VALUE: + case ICAL_METHOD_VALUE: + case ICAL_QUERY_VALUE: + case ICAL_QUERYLEVEL_VALUE: + case ICAL_STATUS_VALUE: + case ICAL_STRING_VALUE: + case ICAL_TRANSP_VALUE: + case ICAL_URI_VALUE: + case ICAL_UTCOFFSET_VALUE: + { + char *str = icalvalue_as_ical_string_r(val); + json_object_array_add(jarray, json_object_new_string(element)); + json_object_array_add(jarray, json_object_new_string(str)); + icalmemory_free_buffer(str); + + break; + } + case ICAL_GEO_VALUE: { + struct icalgeotype geo = icalvalue_get_geo(val); + json_object *jvalue = json_object_new_array(); + + json_object_array_add(jarray, json_object_new_string("float")); + + json_object_array_add(jvalue, json_object_new_double(geo.lat)); + json_object_array_add(jvalue, json_object_new_double(geo.lon)); + + json_object_array_add(jarray, jvalue); + + break; + } + case ICAL_TEXT_VALUE: { + /* we explicitly don't escape text here */ + const char* text = icalvalue_get_text(val); + json_object_array_add(jarray, json_object_new_string(element)); + json_object_array_add(jarray, json_object_new_string(text)); + + break; + } + case ICAL_REQUESTSTATUS_VALUE: { + struct icalreqstattype requeststatus = icalvalue_get_requeststatus(val); + json_object *jvalue = json_object_new_array(); + + json_object_array_add(jarray, json_object_new_string("text")); + + json_object_array_add(jvalue, json_object_new_string(icalenum_reqstat_code(requeststatus.code))); + json_object_array_add(jvalue, json_object_new_string(requeststatus.desc)); + + if (requeststatus.debug) { + json_object_array_add(jvalue, json_object_new_string(requeststatus.debug)); + } + + json_object_array_add(jarray, jvalue); + + break; + } + case ICAL_PERIOD_VALUE: { + struct icalperiodtype period = icalvalue_get_period(val); + json_object *jvalue = json_object_new_array(); + + json_object_array_add(jarray, json_object_new_string(element)); + + rv = icaltime_to_json(f, "start", period.start, jvalue); + if (rv != APR_SUCCESS) { + return rv; + } + + if (!icaltime_is_null_time(period.end)) { + icaltime_to_json(f, "end", period.start, jvalue); + } + else { + icalduration_to_json("duration", period.duration, jvalue); + } + + json_object_array_add(jarray, jvalue); + + break; + } + case ICAL_DATETIMEPERIOD_VALUE: { + struct icaldatetimeperiodtype datetimeperiod = + icalvalue_get_datetimeperiod(val); + json_object *jvalue = json_object_new_array(); + + json_object_array_add(jarray, json_object_new_string(element)); + + if (!icaltime_is_null_time(datetimeperiod.time)) { + icaltime_to_json(f, "time", datetimeperiod.time, jvalue); + } + else { + rv = icaltime_to_json(f, "start", datetimeperiod.period.start, + jvalue); + if (rv != APR_SUCCESS) { + return rv; + } + + if (!icaltime_is_null_time(datetimeperiod.period.end)) { + icaltime_to_json(f, "end", datetimeperiod.period.start, jvalue); + } + else { + icalduration_to_json("duration", + datetimeperiod.period.duration, jvalue); + } + } + + json_object_array_add(jarray, jvalue); + + break; + } + case ICAL_DURATION_VALUE: { + struct icaldurationtype duration = icalvalue_get_duration(val); + + json_object_array_add(jarray, json_object_new_string(element)); + json_object_array_add(jarray, + json_object_new_string( + icaldurationtype_as_ical_string(duration))); + + break; + } + case ICAL_X_VALUE: { + const char* x = icalvalue_get_x(val); + + json_object_array_add(jarray, json_object_new_string(element)); + json_object_array_add(jarray, json_object_new_string(x)); + + break; + } + case ICAL_RECUR_VALUE: { + struct icalrecurrencetype recur = icalvalue_get_recur(val); + + json_object_array_add(jarray, json_object_new_string(element)); + rv = icalrecurrencetype_to_json(f, &recur, jarray); + if (rv != APR_SUCCESS) { + return rv; + } + + break; + } + case ICAL_TRIGGER_VALUE: { + struct icaltriggertype trigger = icalvalue_get_trigger(val); + + json_object_array_add(jarray, json_object_new_string(element)); + if (!icaltime_is_null_time(trigger.time)) { + icaltime_to_json(f, "time", trigger.time, jarray); + } + else { + icalduration_to_json("duration", trigger.duration, jarray); + } + + break; + } + case ICAL_DATE_VALUE: { + struct icaltimetype date = icalvalue_get_date(val); + + json_object_array_add(jarray, json_object_new_string(element)); + json_object_array_add(jarray, + json_object_new_string( + apr_psprintf(f->r->pool, "%04d-%02d-%02d", + date.year, date.month, date.day))); + + break; + } + case ICAL_DATETIME_VALUE: { + struct icaltimetype datetime = icalvalue_get_datetime(val); + + json_object_array_add(jarray, json_object_new_string(element)); + json_object_array_add(jarray, + json_object_new_string( + apr_psprintf(f->r->pool, + "%04d-%02d-%02dT%02d:%02d:%02d", + datetime.year, datetime.month, datetime.day, + datetime.hour, datetime.minute, + datetime.second))); + + break; + } + default: { + /* if we don't recognise it, add it as a string */ + char *str = icalvalue_as_ical_string_r(val); + json_object_array_add(jarray, json_object_new_string(element)); + json_object_array_add(jarray, json_object_new_string(str)); + icalmemory_free_buffer(str); + + break; + } + } + + } + + return rv; +} + +static apr_status_t icalvalue_to_xml(ap_filter_t *f, icalvalue *val, + xmlTextWriterPtr writer) +{ + apr_status_t rv = APR_SUCCESS; + int rc = 0; + + if (val) { + icalvalue_kind kind = icalvalue_isa(val); + char *element = NULL; + + /* work out the value type */ + if (kind != ICAL_X_VALUE) { + element = apr_pstrdup(f->r->pool, icalvalue_kind_to_string(kind)); + } + if (element) { + element = strlwr(element); + } + else { + element = "unknown"; + } + + /* open value element */ + rc = xmlTextWriterStartElement(writer, BAD_CAST + element); + if (rc < 0) { + return APR_EGENERAL; + } + + /* handle each type */ + switch (kind) { + case ICAL_ACTION_VALUE: + case ICAL_ATTACH_VALUE: + case ICAL_BINARY_VALUE: + case ICAL_BOOLEAN_VALUE: + case ICAL_CALADDRESS_VALUE: + case ICAL_CARLEVEL_VALUE: + case ICAL_CLASS_VALUE: + case ICAL_CMD_VALUE: + case ICAL_FLOAT_VALUE: + case ICAL_INTEGER_VALUE: + case ICAL_METHOD_VALUE: + case ICAL_QUERY_VALUE: + case ICAL_QUERYLEVEL_VALUE: + case ICAL_STATUS_VALUE: + case ICAL_STRING_VALUE: + case ICAL_TRANSP_VALUE: + case ICAL_URI_VALUE: + case ICAL_UTCOFFSET_VALUE: + { + char *str = icalvalue_as_ical_string_r(val); + rc = xmlTextWriterWriteFormatRaw(writer, "%s", str); + icalmemory_free_buffer(str); + + break; + } + case ICAL_GEO_VALUE: { + struct icalgeotype geo = icalvalue_get_geo(val); + + rc = xmlTextWriterStartElement(writer, BAD_CAST "latitude"); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterWriteFormatRaw(writer, "%f", geo.lat); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterStartElement(writer, BAD_CAST "longitude"); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterWriteFormatRaw(writer, "%f", geo.lon); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + break; + } + case ICAL_TEXT_VALUE: { + /* we explicitly don't escape text here */ + const char* text = icalvalue_get_text(val); + rc = xmlTextWriterWriteFormatRaw(writer, "%s", text); + + break; + } + case ICAL_REQUESTSTATUS_VALUE: { + struct icalreqstattype requeststatus = icalvalue_get_requeststatus(val); + + rc = xmlTextWriterStartElement(writer, BAD_CAST "code"); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterWriteFormatRaw(writer, "%s", + icalenum_reqstat_code(requeststatus.code)); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterStartElement(writer, BAD_CAST "description"); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterWriteFormatRaw(writer, "%s", requeststatus.desc); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + if (requeststatus.debug) { + + rc = xmlTextWriterStartElement(writer, BAD_CAST "data"); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterWriteFormatRaw(writer, "%s", + requeststatus.debug); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + break; + } + case ICAL_PERIOD_VALUE: { + struct icalperiodtype period = icalvalue_get_period(val); + + rv = icaltime_to_xml("start", period.start, writer); + if (rv != APR_SUCCESS) { + return rv; + } + + if (!icaltime_is_null_time(period.end)) { + icaltime_to_xml("end", period.start, writer); + } + else { + icalduration_to_xml("duration", period.duration, writer); + } + + break; + } + case ICAL_DATETIMEPERIOD_VALUE: { + struct icaldatetimeperiodtype datetimeperiod = + icalvalue_get_datetimeperiod(val); + + if (!icaltime_is_null_time(datetimeperiod.time)) { + icaltime_to_xml("time", datetimeperiod.time, writer); + } + else { + rv = icaltime_to_xml("start", datetimeperiod.period.start, + writer); + if (rv != APR_SUCCESS) { + return rv; + } + + if (!icaltime_is_null_time(datetimeperiod.period.end)) { + icaltime_to_xml("end", datetimeperiod.period.start, writer); + } + else { + icalduration_to_xml("duration", + datetimeperiod.period.duration, writer); + } + } + + break; + } + case ICAL_DURATION_VALUE: { + struct icaldurationtype duration = icalvalue_get_duration(val); + rc = xmlTextWriterWriteFormatRaw(writer, "%s", + icaldurationtype_as_ical_string(duration)); + + break; + } + case ICAL_X_VALUE: { + const char* x = icalvalue_get_x(val); + rc = xmlTextWriterWriteFormatRaw(writer, "%s", x); + break; + } + case ICAL_RECUR_VALUE: { + struct icalrecurrencetype recur = icalvalue_get_recur(val); + + rv = icalrecurrencetype_to_xml(f, &recur, writer); + if (rv != APR_SUCCESS) { + return rv; + } + + break; + } + case ICAL_TRIGGER_VALUE: { + struct icaltriggertype trigger = icalvalue_get_trigger(val); + + if (!icaltime_is_null_time(trigger.time)) { + icaltime_to_xml("time", trigger.time, writer); + } + else { + icalduration_to_xml("duration", trigger.duration, writer); + } + + break; + } + case ICAL_DATE_VALUE: { + struct icaltimetype date = icalvalue_get_date(val); + rc = xmlTextWriterWriteFormatRaw(writer, "%04d-%02d-%02d", + date.year, date.month, date.day); + + break; + } + case ICAL_DATETIME_VALUE: { + struct icaltimetype datetime = icalvalue_get_datetime(val); + rc = xmlTextWriterWriteFormatRaw(writer, "%04d-%02d-%02dT%02d:%02d:%02d", + datetime.year, datetime.month, datetime.day, datetime.hour, + datetime.minute, datetime.second); + + break; + } + default: { + /* if we don't recognise it, write it as a string */ + char *str = icalvalue_as_ical_string_r(val); + rc = xmlTextWriterWriteFormatRaw(writer, "%s", str); + icalmemory_free_buffer(str); + + break; + } + } + if (rc < 0) { + return APR_EGENERAL; + } + + /* close property element */ + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + return rv; +} + +static apr_status_t icalparameter_to_json(ap_filter_t *f, icalparameter *param, + json_object *jobject) +{ + apr_status_t rv = APR_SUCCESS; + + if (param) { + char *element; + const char *str; + icalparameter_kind kind = icalparameter_isa(param); + + /* work out the parameter name */ + if (kind == ICAL_X_PARAMETER) { + element = apr_pstrdup(f->r->pool, icalparameter_get_xname(param)); + } +#ifdef ICAL_IANA_PARAMETER + else if (kind == ICAL_IANA_PARAMETER) { + element = apr_pstrdup(f->r->pool, + icalparameter_get_iana_name(param)); + } +#endif + else { + element = apr_pstrdup(f->r->pool, + icalparameter_kind_to_string(kind)); + } + + /* write parameter */ + str = icalparameter_get_xvalue(param); + if (str) { + json_object_object_add(jobject, strlwr(element), + json_object_new_string(str)); + } + + } + + return rv; +} + +static apr_status_t icalparameter_to_xml(ap_filter_t *f, icalparameter *param, + xmlTextWriterPtr writer) +{ + apr_status_t rv = APR_SUCCESS; + int rc; + + if (param) { + char *element; + const char *str; + icalparameter_kind kind = icalparameter_isa(param); + + /* work out the parameter name */ + if (kind == ICAL_X_PARAMETER) { + element = apr_pstrdup(f->r->pool, icalparameter_get_xname(param)); + } +#ifdef ICAL_IANA_PARAMETER + else if (kind == ICAL_IANA_PARAMETER) { + element = apr_pstrdup(f->r->pool, + icalparameter_get_iana_name(param)); + } +#endif + else { + element = apr_pstrdup(f->r->pool, + icalparameter_kind_to_string(kind)); + } + + /* open parameter element */ + element = strlwr(element); + rc = xmlTextWriterStartElement(writer, BAD_CAST + element); + if (rc < 0) { + return APR_EGENERAL; + } + + /* write parameter */ + str = icalparameter_get_xvalue(param); + if (str) { + rc = xmlTextWriterWriteFormatRaw(writer, "%s", str); + } + + /* close parameter element */ + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + return rv; +} + +static apr_status_t icalproperty_to_json(ap_filter_t *f, icalproperty *prop, + json_object *jarray) +{ + apr_status_t rv = APR_SUCCESS; + + if (prop) { + char *element; + const char *x_name; + icalparameter *sparam; + icalproperty_kind kind = icalproperty_isa(prop); + json_object *jprop, *jparam; + + jprop = json_object_new_array(); + json_object_array_add(jarray, jprop); + + /* work out the parameter name */ + x_name = icalproperty_get_x_name(prop); + if (kind == ICAL_X_PROPERTY && x_name != 0) { + element = apr_pstrdup(f->r->pool, x_name); + } + else { + element = apr_pstrdup(f->r->pool, + icalproperty_kind_to_string(kind)); + } + + /* open property element */ + json_object_array_add(jprop, json_object_new_string(strlwr(element))); + + /* handle parameters */ + jparam = json_object_new_object(); + json_object_array_add(jprop, jparam); + sparam = icalproperty_get_first_parameter(prop, ICAL_ANY_PARAMETER); + if (sparam) { + + while (sparam) { + + rv = icalparameter_to_json(f, sparam, jparam); + if (rv != APR_SUCCESS) { + return rv; + } + + sparam = icalproperty_get_next_parameter(prop, ICAL_ANY_PARAMETER); + } + + } + + /* handle value */ + switch (kind) { + case ICAL_CATEGORIES_PROPERTY: + case ICAL_RESOURCES_PROPERTY: + case ICAL_FREEBUSY_PROPERTY: + case ICAL_EXDATE_PROPERTY: + case ICAL_RDATE_PROPERTY: { + rv = icalvalue_multi_to_json(f, icalproperty_get_value(prop), jprop); + if (rv != APR_SUCCESS) { + return rv; + } + break; + } + default: { + rv = icalvalue_to_json(f, icalproperty_get_value(prop), jprop); + if (rv != APR_SUCCESS) { + return rv; + } + break; + } + } + + } + + return rv; +} + +static apr_status_t icalproperty_to_xml(ap_filter_t *f, icalproperty *prop, + xmlTextWriterPtr writer) +{ + apr_status_t rv = APR_SUCCESS; + int rc; + + if (prop) { + char *element; + const char *x_name; + icalparameter *sparam; + icalproperty_kind kind = icalproperty_isa(prop); + + /* work out the parameter name */ + x_name = icalproperty_get_x_name(prop); + if (kind == ICAL_X_PROPERTY && x_name != 0) { + element = apr_pstrdup(f->r->pool, x_name); + } + else { + element = apr_pstrdup(f->r->pool, + icalproperty_kind_to_string(kind)); + } + + /* open property element */ + element = strlwr(element); + rc = xmlTextWriterStartElement(writer, BAD_CAST + element); + if (rc < 0) { + return APR_EGENERAL; + } + + /* handle parameters */ + sparam = icalproperty_get_first_parameter(prop, ICAL_ANY_PARAMETER); + if (sparam) { + + rc = xmlTextWriterStartElement(writer, BAD_CAST "parameters"); + if (rc < 0) { + return APR_EGENERAL; + } + + while (sparam) { + + rv = icalparameter_to_xml(f, sparam, writer); + if (rv != APR_SUCCESS) { + return rv; + } + + sparam = icalproperty_get_next_parameter(prop, ICAL_ANY_PARAMETER); + } + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + /* handle value */ + switch (kind) { + case ICAL_CATEGORIES_PROPERTY: + case ICAL_RESOURCES_PROPERTY: + case ICAL_FREEBUSY_PROPERTY: + case ICAL_EXDATE_PROPERTY: + case ICAL_RDATE_PROPERTY: { + rv = icalvalue_multi_to_xml(f, icalproperty_get_value(prop), writer); + if (rv != APR_SUCCESS) { + return rv; + } + break; + } + default: { + rv = icalvalue_to_xml(f, icalproperty_get_value(prop), writer); + if (rv != APR_SUCCESS) { + return rv; + } + break; + } + } + + /* close property element */ + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + return rv; +} + +static apr_status_t icalcomponent_to_json(ap_filter_t *f, icalcomponent *comp, + json_object * array) +{ + apr_status_t rv = APR_SUCCESS; + + if (comp) { + icalcomponent *scomp; + icalproperty *sprop; + char *element; + json_object *jprop, *jcomp; + + /* open component element */ + element = apr_pstrdup(f->r->pool, + icalcomponent_kind_to_string(icalcomponent_isa(comp))); + json_object_array_add(array, json_object_new_string(strlwr(element))); + + /* handle properties */ + jprop = json_object_new_array(); + json_object_array_add(array, jprop); + sprop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY); + if (sprop) { + + while (sprop) { + + rv = icalproperty_to_json(f, sprop, jprop); + if (rv != APR_SUCCESS) { + return rv; + } + + sprop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY); + } + + } + + /* handle components */ + jcomp = json_object_new_array(); + json_object_array_add(array, jcomp); + scomp = icalcomponent_get_first_component (comp, ICAL_ANY_COMPONENT); + if (scomp) { + + while (scomp) { + + rv = icalcomponent_to_json(f, scomp, jcomp); + if (rv != APR_SUCCESS) { + return rv; + } + + scomp = icalcomponent_get_next_component (comp, ICAL_ANY_COMPONENT); + } + + } + + } + + return rv; +} + +static apr_status_t icalcomponent_to_xml(ap_filter_t *f, icalcomponent *comp, + xmlTextWriterPtr writer) +{ + apr_status_t rv = APR_SUCCESS; + int rc; + + if (comp) { + icalcomponent *scomp; + icalproperty *sprop; + char *element; + + /* open component element */ + element = apr_pstrdup(f->r->pool, + icalcomponent_kind_to_string(icalcomponent_isa(comp))); + rc = xmlTextWriterStartElement(writer, BAD_CAST + strlwr(element)); + if (rc < 0) { + return APR_EGENERAL; + } + + /* handle properties */ + sprop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY); + if (sprop) { + + rc = xmlTextWriterStartElement(writer, BAD_CAST "properties"); + if (rc < 0) { + return APR_EGENERAL; + } + + while (sprop) { + + rv = icalproperty_to_xml(f, sprop, writer); + if (rv != APR_SUCCESS) { + return rv; + } + + sprop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY); + } + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + /* handle components */ + scomp = icalcomponent_get_first_component (comp, ICAL_ANY_COMPONENT); + if (scomp) { + + rc = xmlTextWriterStartElement(writer, BAD_CAST "components"); + if (rc < 0) { + return APR_EGENERAL; + } + + while (scomp) { + + rv = icalcomponent_to_xml(f, scomp, writer); + if (rv != APR_SUCCESS) { + return rv; + } + + scomp = icalcomponent_get_next_component (comp, ICAL_ANY_COMPONENT); + } + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + /* close component element */ + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + } + + return rv; +} + +static apr_status_t ical_to_xcal(ap_filter_t *f, icalcomponent *comp) +{ + apr_status_t rv; + int rc; + ical_ctx *ctx = f->ctx; + xmlBufferPtr buf; + xmlTextWriterPtr writer; + + buf = xmlBufferCreate(); + if (buf == NULL) { + return APR_ENOMEM; + } + apr_pool_cleanup_register(f->r->pool, buf, xmlbuffer_cleanup, + apr_pool_cleanup_null); + + writer = xmlNewTextWriterMemory(buf, 0); + if (writer == NULL) { + return APR_ENOMEM; + } + apr_pool_cleanup_register(f->r->pool, writer, xmlwriter_cleanup, + apr_pool_cleanup_null); + + if (ctx->format == AP_ICAL_FORMAT_PRETTY + || ctx->format == AP_ICAL_FORMAT_SPACED) { + xmlTextWriterSetIndent(writer, 1); + xmlTextWriterSetIndentString (writer, BAD_CAST " "); + } + + rc = xmlTextWriterStartDocument(writer, NULL, "UTF-8", NULL); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterStartElementNS(writer, NULL, BAD_CAST "icalendar", BAD_CAST "urn:ietf:params:xml:ns:icalendar-2.0"); + if (rc < 0) { + return APR_EGENERAL; + } + + rv = icalcomponent_to_xml(f, comp, writer); + if (rv != APR_SUCCESS) { + return rv; + } + + rc = xmlTextWriterEndElement(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + rc = xmlTextWriterEndDocument(writer); + if (rc < 0) { + return APR_EGENERAL; + } + + apr_pool_cleanup_run(f->r->pool, writer, xmlwriter_cleanup); + + rv = apr_brigade_puts(ctx->bb, NULL, NULL, (const char *) buf->content); + if (rv != APR_SUCCESS) { + return rv; + } + + apr_pool_cleanup_run(f->r->pool, buf, xmlbuffer_cleanup); + + return APR_SUCCESS; +} + +static apr_status_t ical_to_jcal(ap_filter_t *f, icalcomponent *comp) +{ + apr_status_t rv; + ical_ctx *ctx = f->ctx; + json_object * jarray; + const char *str; + + jarray = json_object_new_array(); + if (jarray == NULL) { + return APR_ENOMEM; + } + apr_pool_cleanup_register(f->r->pool, jarray, jsonbuffer_cleanup, + apr_pool_cleanup_null); + + rv = icalcomponent_to_json(f, comp, jarray); + if (rv != APR_SUCCESS) { + return rv; + } + + str = json_object_to_json_string_ext(jarray, + ctx->format == AP_ICAL_FORMAT_PRETTY ? JSON_C_TO_STRING_PRETTY : + ctx->format == AP_ICAL_FORMAT_SPACED ? JSON_C_TO_STRING_SPACED : + JSON_C_TO_STRING_PLAIN); + rv = apr_brigade_puts(ctx->bb, NULL, NULL, str); + if (rv != APR_SUCCESS) { + return rv; + } + + apr_pool_cleanup_run(f->r->pool, jarray, jsonbuffer_cleanup); + + return APR_SUCCESS; +} + +static apr_status_t ical_to_ical(ap_filter_t *f, icalcomponent *comp) +{ + apr_status_t rv; + char *temp; + ical_ctx *ctx = f->ctx; + + temp = icalcomponent_as_ical_string_r(comp); + rv = apr_brigade_write(ctx->bb, NULL, NULL, temp, strlen(temp)); + free(temp); + + return rv; +} + +static ap_ical_filter_e parse_filter(const char *arg, apr_off_t len) +{ + if (!strncmp(arg, "none", len)) { + return AP_ICAL_FILTER_NONE; + } + else if (!strncmp(arg, "next", len)) { + return AP_ICAL_FILTER_NEXT; + } + else if (!strncmp(arg, "last", len)) { + return AP_ICAL_FILTER_LAST; + } + else if (!strncmp(arg, "future", len)) { + return AP_ICAL_FILTER_FUTURE; + } + else if (!strncmp(arg, "past", len)) { + return AP_ICAL_FILTER_PAST; + } + else { + return AP_ICAL_FILTER_UNKNOWN; + } +} + +static ap_ical_format_e parse_format(const char *arg, apr_off_t len) +{ + if (!strncmp(arg, "none", len)) { + return AP_ICAL_FORMAT_NONE; + } + else if (!strncmp(arg, "pretty", len)) { + return AP_ICAL_FORMAT_PRETTY; + } + else if (!strncmp(arg, "spaced", len)) { + return AP_ICAL_FORMAT_SPACED; + } + else { + return AP_ICAL_FORMAT_UNKNOWN; + } +} + +static icalcomponent *filter_component(ap_filter_t *f, icalcomponent *comp) +{ + ical_ctx *ctx = f->ctx; + + if (comp) { + icalcomponent *scomp, *candidate = NULL; + + icalcompiter iter = icalcomponent_begin_component(comp, + ICAL_ANY_COMPONENT); + + struct icaltimetype now = icaltime_current_time_with_zone( + icaltimezone_get_utc_timezone()); + + while ((scomp = icalcompiter_next(&iter))) { + + switch (ctx->filter) { + case AP_ICAL_FILTER_NEXT: { + struct icaltimetype end = icalcomponent_get_dtend(scomp); + + /* in the past? */ + if (icaltime_compare(now, end) > 0) { + icalcompiter_next(&iter); + icalcomponent_remove_component(comp, scomp); + icalcomponent_free(scomp); + break; + } + + /* better than candidate? */ + if (candidate) { + if (icaltime_compare(end, + icalcomponent_get_dtend(candidate)) < 0) { + icalcompiter_next(&iter); + icalcomponent_remove_component(comp, candidate); + icalcomponent_free(candidate); + } + } + + /* we are now the best candidate */ + candidate = scomp; + break; + } + case AP_ICAL_FILTER_LAST: { + struct icaltimetype end = icalcomponent_get_dtend(scomp); + + /* in the future? */ + if (icaltime_compare(now, end) < 0) { + icalcompiter_next(&iter); + icalcomponent_remove_component(comp, scomp); + icalcomponent_free(scomp); + break; + } + + /* better than candidate? */ + if (candidate) { + if (icaltime_compare(end, + icalcomponent_get_dtend(candidate)) > 0) { + icalcompiter_next(&iter); + icalcomponent_remove_component(comp, candidate); + icalcomponent_free(candidate); + } + } + + /* we are now the best candidate */ + candidate = scomp; + break; + } + case AP_ICAL_FILTER_FUTURE: { + struct icaltimetype end = icalcomponent_get_dtend(scomp); + + /* in the past? */ + if (icaltime_compare(now, end) > 0) { + icalcompiter_next(&iter); + icalcomponent_remove_component(comp, scomp); + icalcomponent_free(scomp); + break; + } + + break; + } + case AP_ICAL_FILTER_PAST: { + struct icaltimetype end = icalcomponent_get_dtend(scomp); + + /* in the future? */ + if (icaltime_compare(now, end) < 0) { + icalcompiter_next(&iter); + icalcomponent_remove_component(comp, scomp); + icalcomponent_free(scomp); + break; + } + + break; + } + default: { + /* none, passthrough */ + break; + } + } + + } + + } + + return comp; +} + +static icalcomponent *add_line(ap_filter_t *f, ical_ctx *ctx) +{ + char *buffer; + apr_off_t actual; + apr_size_t total; + icalcomponent *comp; + + /* flatten the brigade, terminate with NUL */ + apr_brigade_length(ctx->tmp, 1, &actual); + + total = (apr_size_t) actual; + + buffer = apr_palloc(f->r->pool, total + 1); + buffer[total] = 0; + apr_brigade_flatten(ctx->tmp, buffer, &total); + apr_brigade_cleanup(ctx->tmp); + + /* handle line in ctx->tmp */ + comp = icalparser_add_line(ctx->parser, buffer); + + /* clean up the component */ + if (comp) { + apr_pool_cleanup_register(f->r->pool, comp, icalcomponent_cleanup, + apr_pool_cleanup_null); + } + + return comp; +} + +static apr_status_t ical_header(ap_filter_t *f) +{ + ical_ctx *ctx = f->ctx; + + switch (ctx->output) { + case AP_ICAL_OUTPUT_ICAL: { + break; + } + case AP_ICAL_OUTPUT_XCAL: { + ap_set_content_type(f->r, "application/calendar+xml"); + break; + } + case AP_ICAL_OUTPUT_JCAL: { + ap_set_content_type(f->r, "application/calendar+json"); + break; + } + default: { + break; + } + } + + return APR_SUCCESS; +} + +static apr_status_t ical_footer(ap_filter_t *f) +{ + return APR_SUCCESS; +} + +static apr_status_t ical_query(ap_filter_t *f) +{ + ical_ctx *ctx = f->ctx; + const char *slider = f->r->args; + + ical_conf *conf = ap_get_module_config(f->r->per_dir_config, + &ical_module); + + ctx->filter = conf->filter; + ctx->format = conf->format; + + while (slider && *slider) { + const char *key = slider; + const char *val = ap_strchr(slider, '='); + apr_off_t klen, vlen; + + slider = ap_strchr(slider, '&'); + + if (val) { + + /* isolate the key and value */ + klen = val - key; + val++; + if (slider) { + vlen = slider - val; + slider++; + } + else { + vlen = strlen(val); + } + + /* what have we found? */ + if (!strncmp(key, "filter", klen)) { + + ap_ical_filter_e filter = parse_filter(val, vlen); + if (filter != AP_ICAL_FILTER_UNKNOWN) { + ctx->filter = filter; + } + + } + + if (!strncmp(key, "format", klen)) { + + ap_ical_format_e format = parse_format(val, vlen); + if (format != AP_ICAL_FORMAT_UNKNOWN) { + ctx->format = format; + } + + } + + } + + }; + + return APR_SUCCESS; +} + +static apr_status_t ical_write(ap_filter_t *f, icalcomponent *comp) +{ + ical_ctx *ctx = f->ctx; + apr_status_t rv; + + switch (ctx->output) { + case AP_ICAL_OUTPUT_ICAL: { + rv = ical_to_ical(f, comp); + break; + } + case AP_ICAL_OUTPUT_XCAL: { + rv = ical_to_xcal(f, comp); + break; + } + case AP_ICAL_OUTPUT_JCAL: { + rv = ical_to_jcal(f, comp); + break; + } + default: { + rv = APR_ENOTIMPL; + break; + } + } + + return rv; +} + +static int ical_out_setup(ap_filter_t *f) +{ + ical_ctx *ctx; + + ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(ical_ctx)); + ctx->output = AP_ICAL_OUTPUT_NEGOTIATED; + + return APR_SUCCESS; +} + +static int ical_out_ical_setup(ap_filter_t *f) +{ + ical_ctx *ctx; + + ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(ical_ctx)); + ctx->output = AP_ICAL_OUTPUT_ICAL; + + return APR_SUCCESS; +} + +static int ical_out_xcal_setup(ap_filter_t *f) +{ + ical_ctx *ctx; + + ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(ical_ctx)); + ctx->output = AP_ICAL_OUTPUT_XCAL; + + return APR_SUCCESS; +} + +static int ical_out_jcal_setup(ap_filter_t *f) +{ + ical_ctx *ctx; + + ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(ical_ctx)); + ctx->output = AP_ICAL_OUTPUT_JCAL; + + return APR_SUCCESS; +} + +static apr_status_t ical_out_filter(ap_filter_t *f, apr_bucket_brigade *bb) +{ + apr_status_t rv = APR_SUCCESS; + apr_bucket *e; + request_rec *r = f->r; + ical_ctx *ctx = f->ctx; + icalcomponent *comp; + + /* first time in? create a parser */ + if (!ctx->parser) { + const char *ct; + + /* sanity check - input must be text/calendar or fail */ + ct = ap_field_noparam(r->pool, r->content_type); + if (!ct || strcasecmp(ct, "text/calendar")) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r, + "unexpected content-type '%s', %s filter needs 'text/calendar', filter disabled", + ct, f->frec->name); + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, ctx->bb); + } + + ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc); + ctx->tmp = apr_brigade_create(r->pool, f->c->bucket_alloc); + + ctx->parser = icalparser_new(); + apr_pool_cleanup_register(r->pool, ctx->parser, icalparser_cleanup, + apr_pool_cleanup_null); + + /* must we negotiate the output format? */ + if (ctx->output == AP_ICAL_OUTPUT_NEGOTIATED) { + const char *accept = apr_table_get(r->headers_in, "Accept"); + + if (!strcmp(accept, "text/calendar")) { + ctx->output = AP_ICAL_OUTPUT_ICAL; + } + else if (!strcmp(accept, "application/calendar+xml")) { + ctx->output = AP_ICAL_OUTPUT_XCAL; + } + else if (!strcmp(accept, "application/calendar+json")) { + ctx->output = AP_ICAL_OUTPUT_JCAL; + } + else { + /* fall back to text/calendar by default */ + ctx->output = AP_ICAL_OUTPUT_ICAL; + } + } + + /* type of filtering/formatting to do */ + ical_query(f); + + rv = ical_header(f); + if (rv != APR_SUCCESS) { + return rv; + } + + } + + while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(bb)) { + const char *data; + apr_size_t size; + + e = APR_BRIGADE_FIRST(bb); + + /* EOS means we are done. */ + if (APR_BUCKET_IS_EOS(e)) { + + /* handle last line */ + comp = filter_component(f, add_line(f, ctx)); + if (comp) { + + rv = ical_write(f, comp); + if (rv != APR_SUCCESS) { + return rv; + } + + rv = ical_footer(f); + if (rv != APR_SUCCESS) { + return rv; + } + + apr_pool_cleanup_run(f->r->pool, comp, icalcomponent_cleanup); + } + + /* pass the EOS across */ + APR_BRIGADE_CONCAT(ctx->bb, bb); + + /* pass what we have down the chain */ + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, ctx->bb); + } + + /* metadata buckets are preserved as is */ + if (APR_BUCKET_IS_METADATA(e)) { + /* + * Remove meta data bucket from old brigade and insert into the + * new. + */ + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(ctx->bb, e); + continue; + } + + /* at this point we are ready to buffer. + * Buffering takes advantage of an optimisation in the handling of + * bucket brigades. Heap buckets are always created at a fixed + * size, regardless of the size of the data placed into them. + * The apr_brigade_write() call will first try and pack the data + * into any free space in the most recent heap bucket, before + * allocating a new bucket if necessary. + */ + if (APR_SUCCESS == (rv = apr_bucket_read(e, &data, &size, + APR_BLOCK_READ))) { + const char *poslf, *poscr; + + if (!size) { + apr_bucket_delete(e); + continue; + } + + if (ctx->eat_crlf) { + if (*data == APR_ASCII_CR || *data == APR_ASCII_LF) { + apr_bucket_split(e, 1); + apr_bucket_delete(e); + continue; + } + ctx->eat_crlf = 0; + } + + if (ctx->seen_eol) { + ctx->seen_eol = 0; + + /* continuation line? */ + if (size + && (*data == APR_ASCII_BLANK || *data == APR_ASCII_TAB)) { + apr_bucket_split(e, 1); + apr_bucket_delete(e); + continue; + } + + /* process the line */ + else if (!APR_BRIGADE_EMPTY(ctx->tmp)) { + comp = filter_component(f, add_line(f, ctx)); + if (comp) { + + rv = ical_write(f, comp); + if (rv != APR_SUCCESS) { + return rv; + } + + rv = ap_pass_brigade(f->next, ctx->bb); + apr_pool_cleanup_run(f->r->pool, comp, + icalcomponent_cleanup); + } + continue; + } + + } + + /* end of line? */ + poscr = memchr(data, APR_ASCII_CR, size); + poslf = memchr(data, APR_ASCII_LF, size); + if (poslf || poscr) { + const char *pos = (!poslf) ? poscr : (!poscr) ? poslf : + (poslf < poscr) ? poslf : poscr; + + /* isolate the string */ + if (pos != data) { + apr_bucket_split(e, pos - data); + apr_bucket_setaside(e, f->r->pool); + } + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(ctx->tmp, e); + + ctx->eat_crlf = 1; + ctx->seen_eol = 1; + } + else { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(ctx->tmp, e); + } + + } + + } + + return rv; +} + +static void *create_ical_config(apr_pool_t *p, char *dummy) +{ + ical_conf *new = (ical_conf *) apr_pcalloc(p, sizeof(ical_conf)); + + new->filter = DEFAULT_ICAL_FILTER; /* default filter */ + new->format = DEFAULT_ICAL_FORMAT; /* default format */ + + return (void *) new; +} + +static void *merge_ical_config(apr_pool_t *p, void *basev, void *addv) +{ + ical_conf *new = (ical_conf *) apr_pcalloc(p, sizeof(ical_conf)); + ical_conf *add = (ical_conf *) addv; + ical_conf *base = (ical_conf *) basev; + + new->filter = (add->filter_set == 0) ? base->filter : add->filter; + new->filter_set = add->filter_set || base->filter_set; + new->format = (add->format_set == 0) ? base->format : add->format; + new->format_set = add->format_set || base->format_set; + + return new; +} + +static const char *set_ical_filter(cmd_parms *cmd, void *dconf, const char *arg) +{ + ical_conf *conf = dconf; + + conf->filter = parse_filter(arg, strlen(arg)); + + if (conf->filter == AP_ICAL_FILTER_UNKNOWN) { + return "ICalFilter must be one of 'none', 'next', 'last', future' or 'past'"; + } + + conf->filter_set = 1; + + return NULL; +} + +static const char *set_ical_format(cmd_parms *cmd, void *dconf, const char *arg) +{ + ical_conf *conf = dconf; + + conf->format = parse_format(arg, strlen(arg)); + + if (conf->format == AP_ICAL_FORMAT_UNKNOWN) { + return "ICalFormat must be one of 'none', 'spaced' or 'pretty'"; + } + + conf->format_set = 1; + + return NULL; +} + +static const command_rec ical_cmds[] = { + AP_INIT_TAKE1("ICalFilter", set_ical_filter, NULL, ACCESS_CONF, + "Set the filtering to 'none', 'next', 'last', future' or 'past'. Defaults to 'past'"), + AP_INIT_TAKE1("ICalFormat", set_ical_format, NULL, ACCESS_CONF, + "Set the formatting to 'none', 'spaced' or 'pretty'. Defaults to 'none'"), + { NULL } +}; + +static void ical_hooks(apr_pool_t* pool) +{ + ap_register_output_filter("ICAL", ical_out_filter, ical_out_setup, + AP_FTYPE_CONTENT_SET); + ap_register_output_filter("ICALICAL", ical_out_filter, ical_out_ical_setup, + AP_FTYPE_CONTENT_SET); + ap_register_output_filter("ICALXCAL", ical_out_filter, ical_out_xcal_setup, + AP_FTYPE_CONTENT_SET); + ap_register_output_filter("ICALJCAL", ical_out_filter, ical_out_jcal_setup, + AP_FTYPE_CONTENT_SET); +} + +module AP_MODULE_DECLARE_DATA ical_module = { + STANDARD20_MODULE_STUFF, + create_ical_config, + merge_ical_config, + NULL, + NULL, + ical_cmds, + ical_hooks +}; + diff --git a/mod_ical.spec.in b/mod_ical.spec.in new file mode 100644 index 0000000..38680ce --- /dev/null +++ b/mod_ical.spec.in @@ -0,0 +1,45 @@ +# RPM Spec file for mod_ical + +%define name mod_ical +%define summary Apache mod_ical module +%define license None +%define version @PACKAGE_VERSION@ +%define release 1 +%define group System Environment/Daemons +%define source %{name}-%{version}.tar.bz2 +%define packager Graham Leggett +%define buildroot %{_builddir}/%{name}-root +%define modroot %{_libdir}/httpd/modules + +Name: %{name} +Version: %{version} +Release: %{release} +Packager: %{packager} +Summary: %{summary} +License: %{license} +Group: %{group} +Source: %{source} +Prefix: %{_prefix} +Buildroot: %{buildroot} +BuildRequires: httpd-devel, libical-devel, json-c-devel, libxml2-devel +Requires: httpd + +%description +The Apache mod_ical module provides a set of filters to +filter iCalendar data and convert it to xCal/jCal. + +%prep +%setup -q +%build +%configure +make + +%install +rm -rf $RPM_BUILD_ROOT +make install DESTDIR=$RPM_BUILD_ROOT + +%clean +rm -rf $RPM_BUILD_ROOT +%files +%{modroot}/mod_ical.so +