From 5ccae85a531443a8d33c4c6f16eaba9495a9ea92 Mon Sep 17 00:00:00 2001 From: minfrin Date: Sat, 17 Oct 2015 19:10:32 +0200 Subject: [PATCH] Initial import of mod_ical. --- AUTHORS | 3 + COPYING | 178 +++ ChangeLog | 5 + INSTALL | 370 ++++++ Makefile.am | 11 + NEWS | 0 README | 0 configure.ac | 58 + debian/changelog | 5 + debian/compat | 1 + debian/control | 13 + debian/copyright | 26 + debian/docs | 2 + debian/mod-ical.dirs | 1 + debian/mod-ical.substvars | 1 + debian/rules | 13 + debian/source/format | 1 + mod_ical.c | 2411 +++++++++++++++++++++++++++++++++++++ mod_ical.spec.in | 45 + 19 files changed, 3144 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 configure.ac create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/docs create mode 100644 debian/mod-ical.dirs create mode 100644 debian/mod-ical.substvars create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 mod_ical.c create mode 100644 mod_ical.spec.in 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 +