summaryrefslogtreecommitdiff
path: root/modules/cache
diff options
context:
space:
mode:
Diffstat (limited to 'modules/cache')
-rw-r--r--modules/cache/.indent.pro54
-rw-r--r--modules/cache/Makefile.in3
-rw-r--r--modules/cache/NWGNUdsk_cach266
-rw-r--r--modules/cache/NWGNUmakefile246
-rw-r--r--modules/cache/NWGNUmem_cach270
-rw-r--r--modules/cache/NWGNUmod_cach269
-rw-r--r--modules/cache/cache_cache.c171
-rw-r--r--modules/cache/cache_cache.h111
-rw-r--r--modules/cache/cache_hash.c290
-rw-r--r--modules/cache/cache_hash.h159
-rw-r--r--modules/cache/cache_pqueue.c290
-rw-r--r--modules/cache/cache_pqueue.h170
-rw-r--r--modules/cache/cache_storage.c433
-rw-r--r--modules/cache/cache_util.c578
-rw-r--r--modules/cache/config.m426
-rw-r--r--modules/cache/mod_cache.c1249
-rw-r--r--modules/cache/mod_cache.dsp143
-rw-r--r--modules/cache/mod_cache.h327
-rw-r--r--modules/cache/mod_cache.imp9
-rw-r--r--modules/cache/mod_disk_cache.c1174
-rw-r--r--modules/cache/mod_disk_cache.dsp103
-rw-r--r--modules/cache/mod_disk_cache.h95
-rw-r--r--modules/cache/mod_file_cache.c417
-rw-r--r--modules/cache/mod_file_cache.dsp99
-rw-r--r--modules/cache/mod_file_cache.exp1
-rw-r--r--modules/cache/mod_mem_cache.c1095
-rw-r--r--modules/cache/mod_mem_cache.dsp103
27 files changed, 8151 insertions, 0 deletions
diff --git a/modules/cache/.indent.pro b/modules/cache/.indent.pro
new file mode 100644
index 00000000..a9fbe9f9
--- /dev/null
+++ b/modules/cache/.indent.pro
@@ -0,0 +1,54 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
diff --git a/modules/cache/Makefile.in b/modules/cache/Makefile.in
new file mode 100644
index 00000000..167b343d
--- /dev/null
+++ b/modules/cache/Makefile.in
@@ -0,0 +1,3 @@
+
+include $(top_srcdir)/build/special.mk
+
diff --git a/modules/cache/NWGNUdsk_cach b/modules/cache/NWGNUdsk_cach
new file mode 100644
index 00000000..9dc385e7
--- /dev/null
+++ b/modules/cache/NWGNUdsk_cach
@@ -0,0 +1,266 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)\build\NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/os/NetWare \
+ $(AP_WORK)/server/mpm/NetWare \
+ $(AP_WORK)/srclib/pcre \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = dsk_cach
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Memory Cache Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = dsk_cach
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)\build\NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM = _LibCPrelude
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM = _LibCPostlude
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS = AUTOUNLOAD, PSEUDOPREEMPTION
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/dsk_cach.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_disk_cache.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ libcpre.o \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ mod_cach \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @$(APR)/aprlib.imp \
+ @httpd.imp \
+ @mod_cache.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ disk_cache_module \
+ $(EOLIST)
+
+# @cache.imp \
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
+
+
diff --git a/modules/cache/NWGNUmakefile b/modules/cache/NWGNUmakefile
new file mode 100644
index 00000000..71d7e1ba
--- /dev/null
+++ b/modules/cache/NWGNUmakefile
@@ -0,0 +1,246 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)\build\NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)\build\NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/mod_cach.nlm \
+ $(OBJDIR)/mem_cach.nlm \
+ $(OBJDIR)/dsk_cach.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ copy $(OBJDIR)\*.nlm $(INSTALL)\Apache2\modules\*.*
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
diff --git a/modules/cache/NWGNUmem_cach b/modules/cache/NWGNUmem_cach
new file mode 100644
index 00000000..782d8834
--- /dev/null
+++ b/modules/cache/NWGNUmem_cach
@@ -0,0 +1,270 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)\build\NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/os/NetWare \
+ $(AP_WORK)/server/mpm/NetWare \
+ $(AP_WORK)/srclib/pcre \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ -DDEBUG \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mem_cach
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Memory Cache Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = mem_cach
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)\build\NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM = _LibCPrelude
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM = _LibCPostlude
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS = AUTOUNLOAD, PSEUDOPREEMPTION
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/mem_cach.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_mem_cache.o \
+ $(OBJDIR)/cache_hash.o \
+ $(OBJDIR)/cache_pqueue.o \
+ $(OBJDIR)/cache_cache.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ libcpre.o \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ mod_cach \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @$(APR)/aprlib.imp \
+ @httpd.imp \
+ @mod_cache.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ mem_cache_module \
+ $(EOLIST)
+
+# @cache.imp \
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
+
+
diff --git a/modules/cache/NWGNUmod_cach b/modules/cache/NWGNUmod_cach
new file mode 100644
index 00000000..125c4567
--- /dev/null
+++ b/modules/cache/NWGNUmod_cach
@@ -0,0 +1,269 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)\build\NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/os/NetWare \
+ $(AP_WORK)/server/mpm/NetWare \
+ $(AP_WORK)/srclib/pcre \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ -DDEBUG \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mod_cach
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Cache module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = mod_cach
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)\build\NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM = _LibCPrelude
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM = _LibCPostlude
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS = AUTOUNLOAD, PSEUDOPREEMPTION
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/mod_cach.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/cache_util.o \
+ $(OBJDIR)/cache_storage.o \
+ $(OBJDIR)/mod_cache.o \
+ $(EOLIST)
+
+# $(OBJDIR)/mod_mem_cache.o \
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ libcpre.o \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @$(APR)/aprlib.imp \
+ @httpd.imp \
+ @netware.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ @mod_cache.imp \
+ cache_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
+
+
diff --git a/modules/cache/cache_cache.c b/modules/cache/cache_cache.c
new file mode 100644
index 00000000..6db98f71
--- /dev/null
+++ b/modules/cache/cache_cache.c
@@ -0,0 +1,171 @@
+/* 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.
+ */
+
+#include "apr_general.h"
+
+#include "mod_cache.h"
+#include "cache_hash.h"
+#include "cache_pqueue.h"
+#include "cache_cache.h"
+
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#if APR_HAVE_STRING_H
+#include <string.h>
+#endif
+
+struct cache_cache_t {
+ int max_entries;
+ apr_size_t max_size;
+ apr_size_t current_size;
+ int total_purges;
+ long queue_clock;
+ cache_hash_t *ht;
+ cache_pqueue_t *pq;
+ cache_pqueue_set_priority set_pri;
+ cache_pqueue_get_priority get_pri;
+ cache_cache_inc_frequency *inc_entry;
+ cache_cache_get_size *size_entry;
+ cache_cache_get_key *key_entry;
+ cache_cache_free *free_entry;
+};
+
+CACHE_DECLARE(cache_cache_t *)cache_init(int max_entries,
+ apr_size_t max_size,
+ cache_pqueue_get_priority get_pri,
+ cache_pqueue_set_priority set_pri,
+ cache_pqueue_getpos get_pos,
+ cache_pqueue_setpos set_pos,
+ cache_cache_inc_frequency *inc_entry,
+ cache_cache_get_size *size_entry,
+ cache_cache_get_key* key_entry,
+ cache_cache_free *free_entry)
+{
+ cache_cache_t *tmp;
+ tmp = malloc(sizeof(cache_cache_t));
+ tmp->max_entries = max_entries;
+ tmp->max_size = max_size;
+ tmp->current_size = 0;
+ tmp->total_purges = 0;
+ tmp->queue_clock = 0;
+ tmp->get_pri = get_pri;
+ tmp->set_pri = set_pri;
+ tmp->inc_entry = inc_entry;
+ tmp->size_entry = size_entry;
+ tmp->key_entry = key_entry;
+ tmp->free_entry = free_entry;
+
+ tmp->ht = cache_hash_make(max_entries);
+ tmp->pq = cache_pq_init(max_entries, get_pri, get_pos, set_pos);
+
+ return tmp;
+}
+
+CACHE_DECLARE(void) cache_free(cache_cache_t *c)
+{
+ cache_pq_free(c->pq);
+ cache_hash_free(c->ht);
+ free(c);
+}
+
+
+CACHE_DECLARE(void*) cache_find(cache_cache_t* c, const char *key)
+{
+ void *e;
+
+ e = cache_hash_get(c->ht, key, CACHE_HASH_KEY_STRING);
+ if (!e)
+ return NULL;
+
+ return e;
+}
+
+CACHE_DECLARE(void) cache_update(cache_cache_t* c, void *entry)
+{
+ long old_priority;
+ long new_priority;
+
+ old_priority = c->set_pri(c->queue_clock, entry);
+ c->inc_entry(entry);
+ new_priority = c->set_pri(c->queue_clock, entry);
+ cache_pq_change_priority(c->pq, old_priority, new_priority, entry);
+}
+
+CACHE_DECLARE(void) cache_insert(cache_cache_t* c, void *entry)
+{
+ void *ejected = NULL;
+ long priority;
+
+ c->set_pri(c->queue_clock, entry);
+ /* FIX: check if priority of bottom item is greater than inserted one */
+ while ((cache_pq_size(c->pq) >= c->max_entries) ||
+ ((c->current_size + c->size_entry(entry)) > c->max_size)) {
+
+ ejected = cache_pq_pop(c->pq);
+ /* FIX: If ejected is NULL, we'll segfault here */
+ priority = c->get_pri(ejected);
+
+ if (c->queue_clock > priority)
+ c->queue_clock = priority;
+
+ cache_hash_set(c->ht,
+ c->key_entry(ejected),
+ CACHE_HASH_KEY_STRING,
+ NULL);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "Cache Purge of %s",c->key_entry(ejected));
+ c->current_size -= c->size_entry(ejected);
+ c->free_entry(ejected);
+ c->total_purges++;
+ }
+ c->current_size += c->size_entry(entry);
+
+ cache_pq_insert(c->pq, entry);
+ cache_hash_set(c->ht, c->key_entry(entry), CACHE_HASH_KEY_STRING, entry);
+}
+
+CACHE_DECLARE(void *) cache_pop(cache_cache_t *c)
+{
+ void *entry;
+
+ if (!c)
+ return NULL;
+
+ entry = cache_pq_pop(c->pq);
+
+ if (!entry)
+ return NULL;
+
+ c->current_size -= c->size_entry(entry);
+ cache_hash_set(c->ht, c->key_entry(entry), CACHE_HASH_KEY_STRING, NULL);
+
+ return entry;
+}
+
+CACHE_DECLARE(apr_status_t) cache_remove(cache_cache_t *c, void *entry)
+{
+ apr_size_t entry_size = c->size_entry(entry);
+ apr_status_t rc;
+ rc = cache_pq_remove(c->pq, entry);
+ if (rc != APR_SUCCESS)
+ return rc;
+
+ cache_hash_set(c->ht, c->key_entry(entry), CACHE_HASH_KEY_STRING, NULL);
+ c->current_size -= entry_size;
+
+ return APR_SUCCESS;
+}
diff --git a/modules/cache/cache_cache.h b/modules/cache/cache_cache.h
new file mode 100644
index 00000000..042c5d50
--- /dev/null
+++ b/modules/cache/cache_cache.h
@@ -0,0 +1,111 @@
+/* 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.
+ */
+
+/**
+ * @file cache_cache.h
+ * @brief Cache Cache Functions
+ *
+ * @defgroup Cache_cache Cache Functions
+ * @ingroup MOD_CACHE
+ * @{
+ */
+
+#ifndef CACHE_CACHE_H
+#define CACHE_CACHE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "mod_cache.h"
+
+/** ADT for the cache */
+typedef struct cache_cache_t cache_cache_t;
+
+/** callback to increment the frequency of a item */
+typedef void cache_cache_inc_frequency(void*a);
+/** callback to get the size of a item */
+typedef apr_size_t cache_cache_get_size(void*a);
+/** callback to get the key of a item */
+typedef const char* cache_cache_get_key(void *a);
+/** callback to free an entry */
+typedef void cache_cache_free(void *a);
+
+/**
+ * initialize the cache ADT
+ * @param max_entries the number of entries in the cache
+ * @param max_size the size of the cache
+ * @param get_pri callback to get a priority of a entry
+ * @param set_pri callback to set a priority of a entry
+ * @param get_pos callback to get the position of a entry in the cache
+ * @param set_pos callback to set the position of a entry in the cache
+ * @param inc_entry callback to increment the frequency of a entry
+ * @param size_entry callback to get the size of a entry
+ * @param key_entry callback to get the key of a entry
+ * @param free_entry callback to free an entry
+ */
+CACHE_DECLARE(cache_cache_t *)cache_init(int max_entries,
+ apr_size_t max_size,
+ cache_pqueue_get_priority get_pri,
+ cache_pqueue_set_priority set_pri,
+ cache_pqueue_getpos get_pos,
+ cache_pqueue_setpos set_pos,
+ cache_cache_inc_frequency *inc_entry,
+ cache_cache_get_size *size_entry,
+ cache_cache_get_key *key_entry,
+ cache_cache_free *free_entry);
+
+/**
+ * free up the cache
+ * @param c the cache
+ */
+CACHE_DECLARE(void) cache_free(cache_cache_t *c);
+/**
+ * find a entry in the cache, incrementing the frequency if found
+ * @param c the cache
+ * @param key the key
+ */
+CACHE_DECLARE(void*) cache_find(cache_cache_t* c, const char *key);
+/**
+ * insert a entry into the cache
+ * @param c the cache
+ * @param entry the entry
+ */
+CACHE_DECLARE(void) cache_update(cache_cache_t* c, void *entry);
+/**
+ * insert a entry into the cache
+ * @param c the cache
+ * @param entry the entry
+ */
+CACHE_DECLARE(void) cache_insert(cache_cache_t* c, void *entry);
+/**
+ * pop the lowest priority item off
+ * @param c the cache
+ * @returns the entry or NULL
+ */
+CACHE_DECLARE(void *)cache_pop(cache_cache_t* c);
+/**
+ * remove an item from the cache
+ * @param c the cache
+ * @param entry the actual entry (from a find)
+ */
+CACHE_DECLARE(apr_status_t) cache_remove(cache_cache_t* c, void *entry);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !CACHE_CACHE_H */
+/** @} */
diff --git a/modules/cache/cache_hash.c b/modules/cache/cache_hash.c
new file mode 100644
index 00000000..2ac26ec8
--- /dev/null
+++ b/modules/cache/cache_hash.c
@@ -0,0 +1,290 @@
+/* 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.
+ */
+
+#include "apr_general.h"
+
+#include "mod_cache.h"
+#include "cache_hash.h"
+
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#if APR_HAVE_STRING_H
+#include <string.h>
+#endif
+
+
+/*
+ * The internal form of a hash table.
+ *
+ * The table is an array indexed by the hash of the key; collisions
+ * are resolved by hanging a linked list of hash entries off each
+ * element of the array. Although this is a really simple design it
+ * isn't too bad given that pools have a low allocation overhead.
+ */
+
+typedef struct cache_hash_entry_t cache_hash_entry_t;
+
+struct cache_hash_entry_t {
+ cache_hash_entry_t *next;
+ unsigned int hash;
+ const void *key;
+ apr_ssize_t klen;
+ const void *val;
+};
+
+/*
+ * Data structure for iterating through a hash table.
+ *
+ * We keep a pointer to the next hash entry here to allow the current
+ * hash entry to be freed or otherwise mangled between calls to
+ * cache_hash_next().
+ */
+struct cache_hash_index_t {
+ cache_hash_t *ht;
+ cache_hash_entry_t *this, *next;
+ int index;
+};
+
+/*
+ * The size of the array is always a power of two. We use the maximum
+ * index rather than the size so that we can use bitwise-AND for
+ * modular arithmetic.
+ * The count of hash entries may be greater depending on the chosen
+ * collision rate.
+ */
+struct cache_hash_t {
+ cache_hash_entry_t **array;
+ cache_hash_index_t iterator; /* For cache_hash_first(NULL, ...) */
+ int count, max;
+};
+
+/*
+ * Hash creation functions.
+ */
+static cache_hash_entry_t **alloc_array(cache_hash_t *ht, int max)
+{
+ return calloc(1, sizeof(*ht->array) * (max + 1));
+}
+
+CACHE_DECLARE(cache_hash_t *) cache_hash_make(apr_size_t size)
+{
+ cache_hash_t *ht;
+ ht = malloc(sizeof(cache_hash_t));
+ if (!ht) {
+ return NULL;
+ }
+ ht->count = 0;
+ ht->max = size;
+ ht->array = alloc_array(ht, ht->max);
+ if (!ht->array) {
+ free(ht);
+ return NULL;
+ }
+ return ht;
+}
+
+CACHE_DECLARE(void) cache_hash_free(cache_hash_t *ht)
+{
+ if (ht) {
+ if (ht->array) {
+ free (ht->array);
+ }
+ free (ht);
+ }
+}
+/*
+ * Hash iteration functions.
+ */
+
+CACHE_DECLARE(cache_hash_index_t *) cache_hash_next(cache_hash_index_t *hi)
+{
+ hi->this = hi->next;
+ while (!hi->this) {
+ if (hi->index > hi->ht->max)
+ return NULL;
+ hi->this = hi->ht->array[hi->index++];
+ }
+ hi->next = hi->this->next;
+ return hi;
+}
+
+CACHE_DECLARE(cache_hash_index_t *) cache_hash_first(cache_hash_t *ht)
+{
+ cache_hash_index_t *hi;
+
+ hi = &ht->iterator;
+ hi->ht = ht;
+ hi->index = 0;
+ hi->this = NULL;
+ hi->next = NULL;
+ return cache_hash_next(hi);
+}
+
+CACHE_DECLARE(void) cache_hash_this(cache_hash_index_t *hi,
+ const void **key,
+ apr_ssize_t *klen,
+ void **val)
+{
+ if (key) *key = hi->this->key;
+ if (klen) *klen = hi->this->klen;
+ if (val) *val = (void *)hi->this->val;
+}
+
+
+/*
+ * This is where we keep the details of the hash function and control
+ * the maximum collision rate.
+ *
+ * If val is non-NULL it creates and initializes a new hash entry if
+ * there isn't already one there; it returns an updatable pointer so
+ * that hash entries can be removed.
+ */
+
+static cache_hash_entry_t **find_entry(cache_hash_t *ht,
+ const void *key,
+ apr_ssize_t klen,
+ const void *val)
+{
+ cache_hash_entry_t **hep, *he;
+ const unsigned char *p;
+ unsigned int hash;
+ apr_ssize_t i;
+
+ /*
+ * This is the popular `times 33' hash algorithm which is used by
+ * perl and also appears in Berkeley DB. This is one of the best
+ * known hash functions for strings because it is both computed
+ * very fast and distributes very well.
+ *
+ * The originator may be Dan Bernstein but the code in Berkeley DB
+ * cites Chris Torek as the source. The best citation I have found
+ * is "Chris Torek, Hash function for text in C, Usenet message
+ * <27038@mimsy.umd.edu> in comp.lang.c , October, 1990." in Rich
+ * Salz's USENIX 1992 paper about INN which can be found at
+ * <http://citeseer.nj.nec.com/salz92internetnews.html>.
+ *
+ * The magic of number 33, i.e. why it works better than many other
+ * constants, prime or not, has never been adequately explained by
+ * anyone. So I try an explanation: if one experimentally tests all
+ * multipliers between 1 and 256 (as I did while writing a low-level
+ * data structure library some time ago) one detects that even
+ * numbers are not useable at all. The remaining 128 odd numbers
+ * (except for the number 1) work more or less all equally well.
+ * They all distribute in an acceptable way and this way fill a hash
+ * table with an average percent of approx. 86%.
+ *
+ * If one compares the chi^2 values of the variants (see
+ * Bob Jenkins ``Hashing Frequently Asked Questions'' at
+ * http://burtleburtle.net/bob/hash/hashfaq.html for a description
+ * of chi^2), the number 33 not even has the best value. But the
+ * number 33 and a few other equally good numbers like 17, 31, 63,
+ * 127 and 129 have nevertheless a great advantage to the remaining
+ * numbers in the large set of possible multipliers: their multiply
+ * operation can be replaced by a faster operation based on just one
+ * shift plus either a single addition or subtraction operation. And
+ * because a hash function has to both distribute good _and_ has to
+ * be very fast to compute, those few numbers should be preferred.
+ *
+ * -- Ralf S. Engelschall <rse@engelschall.com>
+ */
+ hash = 0;
+ if (klen == CACHE_HASH_KEY_STRING) {
+ for (p = key; *p; p++) {
+ hash = hash * 33 + *p;
+ }
+ klen = p - (const unsigned char *)key;
+ }
+ else {
+ for (p = key, i = klen; i; i--, p++) {
+ hash = hash * 33 + *p;
+ }
+ }
+
+ /* scan linked list */
+ for (hep = &ht->array[hash % ht->max], he = *hep;
+ he;
+ hep = &he->next, he = *hep) {
+ if (he->hash == hash &&
+ he->klen == klen &&
+ memcmp(he->key, key, klen) == 0)
+ break;
+ }
+ if (he || !val)
+ return hep;
+ /* add a new entry for non-NULL values */
+ he = malloc(sizeof(*he));
+ if (!he) {
+ return NULL;
+ }
+ he->next = NULL;
+ he->hash = hash;
+ he->key = key;
+ he->klen = klen;
+ he->val = val;
+ *hep = he;
+ ht->count++;
+ return hep;
+}
+
+CACHE_DECLARE(void *) cache_hash_get(cache_hash_t *ht,
+ const void *key,
+ apr_ssize_t klen)
+{
+ cache_hash_entry_t *he;
+ he = *find_entry(ht, key, klen, NULL);
+ if (he)
+ return (void *)he->val;
+ else
+ return NULL;
+}
+
+CACHE_DECLARE(void *) cache_hash_set(cache_hash_t *ht,
+ const void *key,
+ apr_ssize_t klen,
+ const void *val)
+{
+ cache_hash_entry_t **hep, *tmp;
+ const void *tval;
+ hep = find_entry(ht, key, klen, val);
+ /* If hep == NULL, then the malloc() in find_entry failed */
+ if (hep && *hep) {
+ if (!val) {
+ /* delete entry */
+ tval = (*hep)->val;
+ tmp = *hep;
+ *hep = (*hep)->next;
+ free(tmp);
+ --ht->count;
+ }
+ else {
+ /* replace entry */
+ tval = (*hep)->val;
+ (*hep)->val = val;
+ }
+ /* Return the object just removed from the cache to let the
+ * caller clean it up. Cast the constness away upon return.
+ */
+ return (void *) tval;
+ }
+ /* else key not present and val==NULL */
+ return NULL;
+}
+
+CACHE_DECLARE(int) cache_hash_count(cache_hash_t *ht)
+{
+ return ht->count;
+}
diff --git a/modules/cache/cache_hash.h b/modules/cache/cache_hash.h
new file mode 100644
index 00000000..4138aca7
--- /dev/null
+++ b/modules/cache/cache_hash.h
@@ -0,0 +1,159 @@
+/* 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.
+ */
+
+/**
+ * @file cache_hash.h
+ * @brief Cache Hash Tables
+ *
+ * @defgroup Cache_Hash Hash Tables
+ * @ingroup MOD_CACHE
+ * @{
+ */
+
+#ifndef CACHE_HASH_H
+#define CACHE_HASH_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "mod_cache.h"
+
+/**
+ * When passing a key to cache_hash_set or cache_hash_get, this value can be
+ * passed to indicate a string-valued key, and have cache_hash compute the
+ * length automatically.
+ *
+ * @remark cache_hash will use strlen(key) for the length. The null-terminator
+ * is not included in the hash value (why throw a constant in?).
+ * Since the hash table merely references the provided key (rather
+ * than copying it), cache_hash_this() will return the null-term'd key.
+ */
+#define CACHE_HASH_KEY_STRING (-1)
+
+/**
+ * Abstract type for hash tables.
+ */
+typedef struct cache_hash_t cache_hash_t;
+
+/**
+ * Abstract type for scanning hash tables.
+ */
+typedef struct cache_hash_index_t cache_hash_index_t;
+
+/**
+ * Create a hash table.
+ * @param size
+ * @return The hash table just created
+ */
+CACHE_DECLARE(cache_hash_t *) cache_hash_make(apr_size_t size);
+
+/**
+ * Create a hash table.
+ * @param *ht Pointer to the hash table to be freed.
+ * @return void
+ * @remark The caller should ensure that all objects have been removed
+ * from the cache prior to calling cache_hash_free(). Objects
+ * not removed from the cache prior to calling cache_hash_free()
+ * will be unaccessable.
+ */
+CACHE_DECLARE(void) cache_hash_free(cache_hash_t *ht);
+
+
+/**
+ * Associate a value with a key in a hash table.
+ * @param ht The hash table
+ * @param key Pointer to the key
+ * @param klen Length of the key. Can be CACHE_HASH_KEY_STRING to use the string length.
+ * @param val Value to associate with the key
+ * @remark If the value is NULL the hash entry is deleted.
+ * @return The value of the deleted cache entry (so the caller can clean it up).
+ */
+CACHE_DECLARE(void *) cache_hash_set(cache_hash_t *ht, const void *key,
+ apr_ssize_t klen, const void *val);
+
+/**
+ * Look up the value associated with a key in a hash table.
+ * @param ht The hash table
+ * @param key Pointer to the key
+ * @param klen Length of the key. Can be CACHE_HASH_KEY_STRING to use the string length.
+ * @return Returns NULL if the key is not present.
+ */
+CACHE_DECLARE(void *) cache_hash_get(cache_hash_t *ht, const void *key,
+ apr_ssize_t klen);
+
+/**
+ * Start iterating over the entries in a hash table.
+ * @param ht The hash table
+ * @example
+ */
+/**
+ * <PRE>
+ *
+ * int sum_values(cache_hash_t *ht)
+ * {
+ * cache_hash_index_t *hi;
+ * void *val;
+ * int sum = 0;
+ * for (hi = cache_hash_first(ht); hi; hi = cache_hash_next(hi)) {
+ * cache_hash_this(hi, NULL, NULL, &val);
+ * sum += *(int *)val;
+ * }
+ * return sum;
+ * }
+ *
+ * There is no restriction on adding or deleting hash entries during an
+ * iteration (although the results may be unpredictable unless all you do
+ * is delete the current entry) and multiple iterations can be in
+ * progress at the same time.
+ * </PRE>
+ */
+CACHE_DECLARE(cache_hash_index_t *) cache_hash_first(cache_hash_t *ht);
+
+/**
+ * Continue iterating over the entries in a hash table.
+ * @param hi The iteration state
+ * @return a pointer to the updated iteration state. NULL if there are no more
+ * entries.
+ */
+CACHE_DECLARE(cache_hash_index_t *) cache_hash_next(cache_hash_index_t *hi);
+
+/**
+ * Get the current entry's details from the iteration state.
+ * @param hi The iteration state
+ * @param key Return pointer for the pointer to the key.
+ * @param klen Return pointer for the key length.
+ * @param val Return pointer for the associated value.
+ * @remark The return pointers should point to a variable that will be set to the
+ * corresponding data, or they may be NULL if the data isn't interesting.
+ */
+CACHE_DECLARE(void) cache_hash_this(cache_hash_index_t *hi, const void **key,
+ apr_ssize_t *klen, void **val);
+
+/**
+ * Get the number of key/value pairs in the hash table.
+ * @param ht The hash table
+ * @return The number of key/value pairs in the hash table.
+ */
+CACHE_DECLARE(int) cache_hash_count(cache_hash_t *ht);
+
+
+/** @} */
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !CACHE_HASH_H */
diff --git a/modules/cache/cache_pqueue.c b/modules/cache/cache_pqueue.c
new file mode 100644
index 00000000..580b47e7
--- /dev/null
+++ b/modules/cache/cache_pqueue.c
@@ -0,0 +1,290 @@
+/* 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.
+ */
+
+#include "apr_general.h"
+
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#if APR_HAVE_STDIO_H
+#include <stdio.h>
+#endif
+
+#if APR_HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include "cache_pqueue.h"
+#define left(i) (2*(i))
+#define right(i) ((2*(i))+1)
+#define parent(i) ((i)/2)
+/*
+ * Priority queue structure
+ */
+struct cache_pqueue_t
+{
+ apr_ssize_t size;
+ apr_ssize_t avail;
+ apr_ssize_t step;
+ cache_pqueue_get_priority pri;
+ cache_pqueue_getpos get;
+ cache_pqueue_setpos set;
+ void **d;
+};
+
+cache_pqueue_t *cache_pq_init(apr_ssize_t n,
+ cache_pqueue_get_priority pri,
+ cache_pqueue_getpos get,
+ cache_pqueue_setpos set)
+{
+ cache_pqueue_t *q;
+
+ if (!(q = malloc(sizeof(cache_pqueue_t)))) {
+ return NULL;
+ }
+
+ /* Need to allocate n+1 elements since element 0 isn't used. */
+ if (!(q->d = malloc(sizeof(void*) * (n+1)))) {
+ free(q);
+ return NULL;
+ }
+ q->avail = q->step = (n+1); /* see comment above about n+1 */
+ q->pri = pri;
+ q->size = 1;
+ q->get = get;
+ q->set = set;
+ return q;
+}
+/*
+ * cleanup
+ */
+void cache_pq_free(cache_pqueue_t *q)
+{
+ free(q->d);
+ free(q);
+}
+/*
+ * pqsize: size of the queue.
+ */
+apr_ssize_t cache_pq_size(cache_pqueue_t *q)
+{
+ /* queue element 0 exists but doesn't count since it isn't used. */
+ return (q->size - 1);
+}
+
+static void cache_pq_bubble_up(cache_pqueue_t *q, apr_ssize_t i)
+{
+ apr_ssize_t parent_node;
+ void *moving_node = q->d[i];
+ long moving_pri = q->pri(moving_node);
+
+ for (parent_node = parent(i);
+ ((i > 1) && (q->pri(q->d[parent_node]) < moving_pri));
+ i = parent_node, parent_node = parent(i))
+ {
+ q->d[i] = q->d[parent_node];
+ q->set(q->d[i], i);
+ }
+
+ q->d[i] = moving_node;
+ q->set(moving_node, i);
+}
+
+static apr_ssize_t maxchild(cache_pqueue_t *q, apr_ssize_t i)
+{
+ apr_ssize_t child_node = left(i);
+
+ if (child_node >= q->size)
+ return 0;
+
+ if ((child_node+1 < q->size) &&
+ (q->pri(q->d[child_node+1]) > q->pri(q->d[child_node])))
+ {
+ child_node++; /* use right child instead of left */
+ }
+
+ return child_node;
+}
+
+static void cache_pq_percolate_down(cache_pqueue_t *q, apr_ssize_t i)
+{
+ apr_ssize_t child_node;
+ void *moving_node = q->d[i];
+ long moving_pri = q->pri(moving_node);
+
+ while ((child_node = maxchild(q, i)) &&
+ (moving_pri < q->pri(q->d[child_node])))
+ {
+ q->d[i] = q->d[child_node];
+ q->set(q->d[i], i);
+ i = child_node;
+ }
+
+ q->d[i] = moving_node;
+ q->set(moving_node, i);
+}
+
+apr_status_t cache_pq_insert(cache_pqueue_t *q, void *d)
+{
+ void *tmp;
+ apr_ssize_t i;
+ apr_ssize_t newsize;
+
+ if (!q) return APR_EGENERAL;
+
+ /* allocate more memory if necessary */
+ if (q->size >= q->avail) {
+ newsize = q->size + q->step;
+ if (!(tmp = realloc(q->d, sizeof(void*) * newsize))) {
+ return APR_EGENERAL;
+ };
+ q->d = tmp;
+ q->avail = newsize;
+ }
+
+ /* insert item */
+ i = q->size++;
+ q->d[i] = d;
+ cache_pq_bubble_up(q, i);
+ return APR_SUCCESS;
+}
+
+/*
+ * move a existing entry to a new priority
+ */
+void cache_pq_change_priority(cache_pqueue_t *q,
+ long old_priority,
+ long new_priority,
+ void *d)
+{
+ apr_ssize_t posn;
+
+ posn = q->get(d);
+ if (new_priority > old_priority)
+ cache_pq_bubble_up(q, posn);
+ else
+ cache_pq_percolate_down(q, posn);
+}
+
+apr_status_t cache_pq_remove(cache_pqueue_t *q, void *d)
+{
+ apr_ssize_t posn = q->get(d);
+ q->d[posn] = q->d[--q->size];
+ if (q->pri(q->d[posn]) > q->pri(d))
+ cache_pq_bubble_up(q, posn);
+ else
+ cache_pq_percolate_down(q, posn);
+
+ return APR_SUCCESS;
+}
+
+void *cache_pq_pop(cache_pqueue_t *q)
+{
+ void *head;
+
+ if (!q || q->size == 1)
+ return NULL;
+
+ head = q->d[1];
+ q->d[1] = q->d[--q->size];
+ cache_pq_percolate_down(q, 1);
+
+ return head;
+}
+
+void *cache_pq_peek(cache_pqueue_t *q)
+{
+ void *d;
+ if (!q || q->size == 1)
+ return NULL;
+ d = q->d[1];
+ return d;
+}
+
+static void cache_pq_set_null( void*d, apr_ssize_t val)
+{
+ /* do nothing */
+}
+
+/*
+ * this is a debug function.. so it's EASY not fast
+ */
+void cache_pq_dump(cache_pqueue_t *q,
+ FILE*out,
+ cache_pqueue_print_entry print)
+{
+ int i;
+
+ fprintf(stdout,"posn\tleft\tright\tparent\tmaxchild\t...\n");
+ for (i = 1; i < q->size ;i++) {
+ fprintf(stdout,
+ "%d\t%d\t%d\t%d\t%" APR_SSIZE_T_FMT "\t",
+ i,
+ left(i), right(i), parent(i),
+ maxchild(q, i));
+ print(out, q->d[i]);
+ }
+}
+
+/*
+ * this is a debug function.. so it's EASY not fast
+ */
+void cache_pq_print(cache_pqueue_t *q,
+ FILE*out,
+ cache_pqueue_print_entry print)
+{
+ cache_pqueue_t *dup;
+ dup = cache_pq_init(q->size, q->pri, q->get, cache_pq_set_null);
+ dup->size = q->size;
+ dup->avail = q->avail;
+ dup->step = q->step;
+
+ memcpy(dup->d, q->d, q->size*sizeof(void*));
+
+ while (cache_pq_size(dup) > 1) {
+ void *e = NULL;
+ e = cache_pq_pop(dup);
+ if (e)
+ print(out, e);
+ else
+ break;
+ }
+ cache_pq_free(dup);
+}
+
+static int cache_pq_subtree_is_valid(cache_pqueue_t *q, int pos)
+{
+ if (left(pos) < q->size) {
+ /* has a left child */
+ if (q->pri(q->d[pos]) < q->pri(q->d[left(pos)]))
+ return 0;
+ if (!cache_pq_subtree_is_valid(q, left(pos)))
+ return 0;
+ }
+ if (right(pos) < q->size) {
+ /* has a right child */
+ if (q->pri(q->d[pos]) < q->pri(q->d[right(pos)]))
+ return 0;
+ if (!cache_pq_subtree_is_valid(q, right(pos)))
+ return 0;
+ }
+ return 1;
+}
+
+int cache_pq_is_valid(cache_pqueue_t *q)
+{
+ return cache_pq_subtree_is_valid(q, 1);
+}
diff --git a/modules/cache/cache_pqueue.h b/modules/cache/cache_pqueue.h
new file mode 100644
index 00000000..13238c03
--- /dev/null
+++ b/modules/cache/cache_pqueue.h
@@ -0,0 +1,170 @@
+/* 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.
+ */
+
+/**
+ * @file cache_pqueue.h
+ * @brief Cache Priority Queue function declarations
+ *
+ * @defgroup MOD_CACHE_QUEUE Priority Queue
+ * @ingroup MOD_CACHE
+ * @{
+ */
+
+#ifndef CACHE_PQUEUE_H
+#define CACHE_PQUEUE_H
+
+#include <apr.h>
+#include <apr_errno.h>
+
+#if APR_HAVE_STDIO_H
+#include <stdio.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** the cache priority queue handle */
+typedef struct cache_pqueue_t cache_pqueue_t;
+
+/**
+ * callback function to assign a priority for a element
+ * @param a the element
+ * @return the score (the lower the score the longer it is kept int the queue)
+ */
+typedef long (*cache_pqueue_set_priority)(long queue_clock, void *a);
+typedef long (*cache_pqueue_get_priority)(void *a);
+
+/** callback function to get a position of a element */
+typedef apr_ssize_t (*cache_pqueue_getpos)(void *a);
+
+/**
+ * callback function to set a position of a element
+ * @param a the element
+ * @param pos the position to set it to
+ */
+typedef void (*cache_pqueue_setpos)(void *a, apr_ssize_t pos);
+
+/** debug callback function to print a entry */
+typedef void (*cache_pqueue_print_entry)(FILE *out, void *a);
+
+/**
+ * initialize the queue
+ *
+ * @param n the initial estimate of the number of queue items for which memory
+ * should be preallocated
+ * @param pri the callback function to run to assign a score to a element
+ * @param get the callback function to get the current element's position
+ * @param set the callback function to set the current element's position
+ *
+ * @Return the handle or NULL for insufficent memory
+ */
+cache_pqueue_t *cache_pq_init(apr_ssize_t n,
+ cache_pqueue_get_priority pri,
+ cache_pqueue_getpos get,
+ cache_pqueue_setpos set);
+/**
+ * free all memory used by the queue
+ * @param q the queue
+ */
+void cache_pq_free(cache_pqueue_t *q);
+/**
+ * return the size of the queue.
+ * @param q the queue
+ */
+apr_ssize_t cache_pq_size(cache_pqueue_t *q);
+
+/**
+ * insert an item into the queue.
+ * @param q the queue
+ * @param d the item
+ * @return APR_SUCCESS on success
+ */
+apr_status_t cache_pq_insert(cache_pqueue_t *q, void *d);
+
+/*
+ * move a existing entry to a different priority
+ * @param q the queue
+ * @param old the old priority
+ * @param d the entry
+ */
+void cache_pq_change_priority(cache_pqueue_t *q,
+ long old_priority,
+ long new_priority,
+ void *d);
+
+/**
+ * pop the highest-ranking item from the queue.
+ * @param p the queue
+ * @param d where to copy the entry to
+ * @return NULL on error, otherwise the entry
+ */
+void *cache_pq_pop(cache_pqueue_t *q);
+
+/**
+ * remove an item from the queue.
+ * @param p the queue
+ * @param d the entry
+ * @return APR_SUCCESS on success
+ */
+apr_status_t cache_pq_remove(cache_pqueue_t *q, void *d);
+
+/**
+ * access highest-ranking item without removing it.
+ * @param q the queue
+ * @param d the entry
+ * @return NULL on error, otherwise the entry
+ */
+void *cache_pq_peek(cache_pqueue_t *q);
+
+/**
+ * print the queue
+ * @internal
+ * DEBUG function only
+ * @param q the queue
+ * @param out the output handle
+ * @param the callback function to print the entry
+ */
+void cache_pq_print(cache_pqueue_t *q,
+ FILE *out,
+ cache_pqueue_print_entry print);
+
+/**
+ * dump the queue and it's internal structure
+ * @internal
+ * debug function only
+ * @param q the queue
+ * @param out the output handle
+ * @param the callback function to print the entry
+ */
+void cache_pq_dump(cache_pqueue_t *q,
+ FILE *out,
+ cache_pqueue_print_entry print);
+
+/**
+ * checks that the pq is in the right order, etc
+ * @internal
+ * debug function only
+ * @param q the queue
+ */
+int cache_pq_is_valid(cache_pqueue_t *q);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !CACHE_PQUEUE_H */
+/** @} */
diff --git a/modules/cache/cache_storage.c b/modules/cache/cache_storage.c
new file mode 100644
index 00000000..83352059
--- /dev/null
+++ b/modules/cache/cache_storage.c
@@ -0,0 +1,433 @@
+/* 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.
+ */
+
+#define CORE_PRIVATE
+
+#include "mod_cache.h"
+
+extern APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key;
+
+extern module AP_MODULE_DECLARE_DATA cache_module;
+
+/* -------------------------------------------------------------- */
+
+/*
+ * delete all URL entities from the cache
+ *
+ */
+int cache_remove_url(cache_request_rec *cache, apr_pool_t *p)
+{
+ cache_provider_list *list;
+ cache_handle_t *h;
+
+ list = cache->providers;
+
+ /* Remove the stale cache entry if present. If not, we're
+ * being called from outside of a request; remove the
+ * non-stalle handle.
+ */
+ h = cache->stale_handle ? cache->stale_handle : cache->handle;
+ if (!h) {
+ return OK;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ "cache: Removing url %s from the cache", h->cache_obj->key);
+
+ /* for each specified cache type, delete the URL */
+ while(list) {
+ list->provider->remove_url(h, p);
+ list = list->next;
+ }
+ return OK;
+}
+
+
+/*
+ * create a new URL entity in the cache
+ *
+ * It is possible to store more than once entity per URL. This
+ * function will always create a new entity, regardless of whether
+ * other entities already exist for the same URL.
+ *
+ * The size of the entity is provided so that a cache module can
+ * decide whether or not it wants to cache this particular entity.
+ * If the size is unknown, a size of -1 should be set.
+ */
+int cache_create_entity(request_rec *r, apr_off_t size)
+{
+ cache_provider_list *list;
+ cache_handle_t *h = apr_pcalloc(r->pool, sizeof(cache_handle_t));
+ char *key;
+ apr_status_t rv;
+ cache_request_rec *cache = (cache_request_rec *)
+ ap_get_module_config(r->request_config, &cache_module);
+
+ rv = cache_generate_key(r, r->pool, &key);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ list = cache->providers;
+ /* for each specified cache type, delete the URL */
+ while (list) {
+ switch (rv = list->provider->create_entity(h, r, key, size)) {
+ case OK: {
+ cache->handle = h;
+ cache->provider = list->provider;
+ cache->provider_name = list->provider_name;
+ return OK;
+ }
+ case DECLINED: {
+ list = list->next;
+ continue;
+ }
+ default: {
+ return rv;
+ }
+ }
+ }
+ return DECLINED;
+}
+
+static int set_cookie_doo_doo(void *v, const char *key, const char *val)
+{
+ apr_table_addn(v, key, val);
+ return 1;
+}
+
+CACHE_DECLARE(void) ap_cache_accept_headers(cache_handle_t *h, request_rec *r,
+ int preserve_orig)
+{
+ apr_table_t *cookie_table, *hdr_copy;
+ const char *v;
+
+ v = apr_table_get(h->resp_hdrs, "Content-Type");
+ if (v) {
+ ap_set_content_type(r, v);
+ apr_table_unset(h->resp_hdrs, "Content-Type");
+ /*
+ * Also unset possible Content-Type headers in r->headers_out and
+ * r->err_headers_out as they may be different to what we have received
+ * from the cache.
+ * Actually they are not needed as r->content_type set by
+ * ap_set_content_type above will be used in the store_headers functions
+ * of the storage providers as a fallback and the HTTP_HEADER filter
+ * does overwrite the Content-Type header with r->content_type anyway.
+ */
+ apr_table_unset(r->headers_out, "Content-Type");
+ apr_table_unset(r->err_headers_out, "Content-Type");
+ }
+
+ /* If the cache gave us a Last-Modified header, we can't just
+ * pass it on blindly because of restrictions on future values.
+ */
+ v = apr_table_get(h->resp_hdrs, "Last-Modified");
+ if (v) {
+ ap_update_mtime(r, apr_date_parse_http(v));
+ ap_set_last_modified(r);
+ apr_table_unset(h->resp_hdrs, "Last-Modified");
+ }
+
+ /* The HTTP specification says that it is legal to merge duplicate
+ * headers into one. Some browsers that support Cookies don't like
+ * merged headers and prefer that each Set-Cookie header is sent
+ * separately. Lets humour those browsers by not merging.
+ * Oh what a pain it is.
+ */
+ cookie_table = apr_table_make(r->pool, 2);
+ apr_table_do(set_cookie_doo_doo, cookie_table, r->err_headers_out,
+ "Set-Cookie", NULL);
+ apr_table_do(set_cookie_doo_doo, cookie_table, h->resp_hdrs,
+ "Set-Cookie", NULL);
+ apr_table_unset(r->err_headers_out, "Set-Cookie");
+ apr_table_unset(h->resp_hdrs, "Set-Cookie");
+
+ if (preserve_orig) {
+ hdr_copy = apr_table_copy(r->pool, h->resp_hdrs);
+ apr_table_overlap(hdr_copy, r->headers_out, APR_OVERLAP_TABLES_SET);
+ r->headers_out = hdr_copy;
+ }
+ else {
+ apr_table_overlap(r->headers_out, h->resp_hdrs, APR_OVERLAP_TABLES_SET);
+ }
+ if (!apr_is_empty_table(cookie_table)) {
+ r->err_headers_out = apr_table_overlay(r->pool, r->err_headers_out,
+ cookie_table);
+ }
+}
+
+/*
+ * select a specific URL entity in the cache
+ *
+ * It is possible to store more than one entity per URL. Content
+ * negotiation is used to select an entity. Once an entity is
+ * selected, details of it are stored in the per request
+ * config to save time when serving the request later.
+ *
+ * This function returns OK if successful, DECLINED if no
+ * cached entity fits the bill.
+ */
+int cache_select(request_rec *r)
+{
+ cache_provider_list *list;
+ apr_status_t rv;
+ cache_handle_t *h;
+ char *key;
+ cache_request_rec *cache = (cache_request_rec *)
+ ap_get_module_config(r->request_config, &cache_module);
+
+ rv = cache_generate_key(r, r->pool, &key);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ /* go through the cache types till we get a match */
+ h = apr_palloc(r->pool, sizeof(cache_handle_t));
+
+ list = cache->providers;
+
+ while (list) {
+ switch ((rv = list->provider->open_entity(h, r, key))) {
+ case OK: {
+ char *vary = NULL;
+ int fresh;
+
+ if (list->provider->recall_headers(h, r) != APR_SUCCESS) {
+ /* TODO: Handle this error */
+ return DECLINED;
+ }
+
+ /*
+ * Check Content-Negotiation - Vary
+ *
+ * At this point we need to make sure that the object we found in
+ * the cache is the same object that would be delivered to the
+ * client, when the effects of content negotiation are taken into
+ * effect.
+ *
+ * In plain english, we want to make sure that a language-negotiated
+ * document in one language is not given to a client asking for a
+ * language negotiated document in a different language by mistake.
+ *
+ * This code makes the assumption that the storage manager will
+ * cache the req_hdrs if the response contains a Vary
+ * header.
+ *
+ * RFC2616 13.6 and 14.44 describe the Vary mechanism.
+ */
+ vary = apr_pstrdup(r->pool, apr_table_get(h->resp_hdrs, "Vary"));
+ while (vary && *vary) {
+ char *name = vary;
+ const char *h1, *h2;
+
+ /* isolate header name */
+ while (*vary && !apr_isspace(*vary) && (*vary != ','))
+ ++vary;
+ while (*vary && (apr_isspace(*vary) || (*vary == ','))) {
+ *vary = '\0';
+ ++vary;
+ }
+
+ /*
+ * is this header in the request and the header in the cached
+ * request identical? If not, we give up and do a straight get
+ */
+ h1 = apr_table_get(r->headers_in, name);
+ h2 = apr_table_get(h->req_hdrs, name);
+ if (h1 == h2) {
+ /* both headers NULL, so a match - do nothing */
+ }
+ else if (h1 && h2 && !strcmp(h1, h2)) {
+ /* both headers exist and are equal - do nothing */
+ }
+ else {
+ /* headers do not match, so Vary failed */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r->server,
+ "cache_select_url(): Vary header mismatch.");
+ return DECLINED;
+ }
+ }
+
+ cache->provider = list->provider;
+ cache->provider_name = list->provider_name;
+
+ /* Is our cached response fresh enough? */
+ fresh = ap_cache_check_freshness(h, r);
+ if (!fresh) {
+ const char *etag, *lastmod;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
+ "Cached response for %s isn't fresh. Adding/replacing "
+ "conditional request headers.", r->uri);
+
+ /* Make response into a conditional */
+ cache->stale_headers = apr_table_copy(r->pool,
+ r->headers_in);
+
+ /* We can only revalidate with our own conditionals: remove the
+ * conditions from the original request.
+ */
+ apr_table_unset(r->headers_in, "If-Match");
+ apr_table_unset(r->headers_in, "If-Modified-Since");
+ apr_table_unset(r->headers_in, "If-None-Match");
+ apr_table_unset(r->headers_in, "If-Range");
+ apr_table_unset(r->headers_in, "If-Unmodified-Since");
+
+ etag = apr_table_get(h->resp_hdrs, "ETag");
+ lastmod = apr_table_get(h->resp_hdrs, "Last-Modified");
+
+ if (etag || lastmod) {
+ /* If we have a cached etag and/or Last-Modified add in
+ * our own conditionals.
+ */
+
+ if (etag) {
+ apr_table_set(r->headers_in, "If-None-Match", etag);
+ }
+
+ if (lastmod) {
+ apr_table_set(r->headers_in, "If-Modified-Since",
+ lastmod);
+ }
+ cache->stale_handle = h;
+ }
+
+ return DECLINED;
+ }
+
+ /* Okay, this response looks okay. Merge in our stuff and go. */
+ ap_cache_accept_headers(h, r, 0);
+
+ cache->handle = h;
+ return OK;
+ }
+ case DECLINED: {
+ /* try again with next cache type */
+ list = list->next;
+ continue;
+ }
+ default: {
+ /* oo-er! an error */
+ return rv;
+ }
+ }
+ }
+ return DECLINED;
+}
+
+apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
+ char**key)
+{
+ char *port_str, *hn, *lcs;
+ const char *hostname, *scheme;
+ int i;
+
+ /*
+ * Use the canonical name to improve cache hit rate, but only if this is
+ * not a proxy request or if this is a reverse proxy request.
+ * We need to handle both cases in the same manner as for the reverse proxy
+ * case we have the following situation:
+ *
+ * If a cached entry is looked up by mod_cache's quick handler r->proxyreq
+ * is still unset in the reverse proxy case as it only gets set in the
+ * translate name hook (either by ProxyPass or mod_rewrite) which is run
+ * after the quick handler hook. This is different to the forward proxy
+ * case where it gets set before the quick handler is run (in the
+ * post_read_request hook).
+ * If a cache entry is created by the CACHE_SAVE filter we always have
+ * r->proxyreq set correctly.
+ * So we must ensure that in the reverse proxy case we use the same code
+ * path and using the canonical name seems to be the right thing to do
+ * in the reverse proxy case.
+ */
+ if (!r->proxyreq || (r->proxyreq == PROXYREQ_REVERSE)) {
+ /* Use _default_ as the hostname if none present, as in mod_vhost */
+ hostname = ap_get_server_name(r);
+ if (!hostname) {
+ hostname = "_default_";
+ }
+ }
+ else if(r->parsed_uri.hostname) {
+ /* Copy the parsed uri hostname */
+ hn = apr_pstrdup(p, r->parsed_uri.hostname);
+ ap_str_tolower(hn);
+ /* const work-around */
+ hostname = hn;
+ }
+ else {
+ /* We are a proxied request, with no hostname. Unlikely
+ * to get very far - but just in case */
+ hostname = "_default_";
+ }
+
+ /*
+ * Copy the scheme, ensuring that it is lower case. If the parsed uri
+ * contains no string or if this is not a proxy request get the http
+ * scheme for this request. As r->parsed_uri.scheme is not set if this
+ * is a reverse proxy request, it is ensured that the cases
+ * "no proxy request" and "reverse proxy request" are handled in the same
+ * manner (see above why this is needed).
+ */
+ if (r->proxyreq && r->parsed_uri.scheme) {
+ /* Copy the scheme and lower-case it */
+ lcs = apr_pstrdup(p, r->parsed_uri.scheme);
+ ap_str_tolower(lcs);
+ /* const work-around */
+ scheme = lcs;
+ }
+ else {
+ scheme = ap_http_scheme(r);
+ }
+
+ /*
+ * If this is a proxy request, but not a reverse proxy request (see comment
+ * above why these cases must be handled in the same manner), copy the
+ * URI's port-string (which may be a service name). If the URI contains
+ * no port-string, use apr-util's notion of the default port for that
+ * scheme - if available. Otherwise use the port-number of the current
+ * server.
+ */
+ if(r->proxyreq && (r->proxyreq != PROXYREQ_REVERSE)) {
+ if (r->parsed_uri.port_str) {
+ port_str = apr_pcalloc(p, strlen(r->parsed_uri.port_str) + 2);
+ port_str[0] = ':';
+ for (i = 0; r->parsed_uri.port_str[i]; i++) {
+ port_str[i + 1] = apr_tolower(r->parsed_uri.port_str[i]);
+ }
+ }
+ else if (apr_uri_port_of_scheme(scheme)) {
+ port_str = apr_psprintf(p, ":%u", apr_uri_port_of_scheme(scheme));
+ }
+ else {
+ /* No port string given in the AbsoluteUri, and we have no
+ * idea what the default port for the scheme is. Leave it
+ * blank and live with the inefficiency of some extra cached
+ * entities.
+ */
+ port_str = "";
+ }
+ }
+ else {
+ /* Use the server port */
+ port_str = apr_psprintf(p, ":%u", ap_get_server_port(r));
+ }
+
+ /* Key format is a URI */
+ *key = apr_pstrcat(p, scheme, "://", hostname, port_str,
+ r->parsed_uri.path, "?", r->args, NULL);
+
+ return APR_SUCCESS;
+}
diff --git a/modules/cache/cache_util.c b/modules/cache/cache_util.c
new file mode 100644
index 00000000..31cc2c46
--- /dev/null
+++ b/modules/cache/cache_util.c
@@ -0,0 +1,578 @@
+/* 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.
+ */
+
+#define CORE_PRIVATE
+
+#include "mod_cache.h"
+
+#include <ap_provider.h>
+
+/* -------------------------------------------------------------- */
+
+extern module AP_MODULE_DECLARE_DATA cache_module;
+
+/* Determine if "url" matches the hostname, scheme and port and path
+ * in "filter". All but the path comparisons are case-insensitive.
+ */
+static int uri_meets_conditions(apr_uri_t filter, int pathlen, apr_uri_t url)
+{
+ /* Compare the hostnames */
+ if(filter.hostname) {
+ if (!url.hostname) {
+ return 0;
+ }
+ else if (strcasecmp(filter.hostname, url.hostname)) {
+ return 0;
+ }
+ }
+
+ /* Compare the schemes */
+ if(filter.scheme) {
+ if (!url.scheme) {
+ return 0;
+ }
+ else if (strcasecmp(filter.scheme, url.scheme)) {
+ return 0;
+ }
+ }
+
+ /* Compare the ports */
+ if(filter.port_str) {
+ if (url.port_str && filter.port != url.port) {
+ return 0;
+ }
+ /* NOTE: ap_port_of_scheme will return 0 if given NULL input */
+ else if (filter.port != apr_uri_port_of_scheme(url.scheme)) {
+ return 0;
+ }
+ }
+ else if(url.port_str && filter.scheme) {
+ if (apr_uri_port_of_scheme(filter.scheme) == url.port) {
+ return 0;
+ }
+ }
+
+ /* Url has met all of the filter conditions so far, determine
+ * if the paths match.
+ */
+ return !strncmp(filter.path, url.path, pathlen);
+}
+
+CACHE_DECLARE(cache_provider_list *)ap_cache_get_providers(request_rec *r,
+ cache_server_conf *conf,
+ apr_uri_t uri)
+{
+ cache_provider_list *providers = NULL;
+ int i;
+
+ /* loop through all the cacheenable entries */
+ for (i = 0; i < conf->cacheenable->nelts; i++) {
+ struct cache_enable *ent =
+ (struct cache_enable *)conf->cacheenable->elts;
+ if (uri_meets_conditions(ent[i].url, ent[i].pathlen, uri)) {
+ /* Fetch from global config and add to the list. */
+ cache_provider *provider;
+ provider = ap_lookup_provider(CACHE_PROVIDER_GROUP, ent[i].type,
+ "0");
+ if (!provider) {
+ /* Log an error! */
+ }
+ else {
+ cache_provider_list *newp;
+ newp = apr_pcalloc(r->pool, sizeof(cache_provider_list));
+ newp->provider_name = ent[i].type;
+ newp->provider = provider;
+
+ if (!providers) {
+ providers = newp;
+ }
+ else {
+ cache_provider_list *last = providers;
+
+ while (last->next) {
+ last = last->next;
+ }
+ last->next = newp;
+ }
+ }
+ }
+ }
+
+ /* then loop through all the cachedisable entries
+ * Looking for urls that contain the full cachedisable url and possibly
+ * more.
+ * This means we are disabling cachedisable url and below...
+ */
+ for (i = 0; i < conf->cachedisable->nelts; i++) {
+ struct cache_disable *ent =
+ (struct cache_disable *)conf->cachedisable->elts;
+ if (uri_meets_conditions(ent[i].url, ent[i].pathlen, uri)) {
+ /* Stop searching now. */
+ return NULL;
+ }
+ }
+
+ return providers;
+}
+
+
+/* do a HTTP/1.1 age calculation */
+CACHE_DECLARE(apr_int64_t) ap_cache_current_age(cache_info *info,
+ const apr_time_t age_value,
+ apr_time_t now)
+{
+ apr_time_t apparent_age, corrected_received_age, response_delay,
+ corrected_initial_age, resident_time, current_age,
+ age_value_usec;
+
+ age_value_usec = apr_time_from_sec(age_value);
+
+ /* Perform an HTTP/1.1 age calculation. (RFC2616 13.2.3) */
+
+ apparent_age = MAX(0, info->response_time - info->date);
+ corrected_received_age = MAX(apparent_age, age_value_usec);
+ response_delay = info->response_time - info->request_time;
+ corrected_initial_age = corrected_received_age + response_delay;
+ resident_time = now - info->response_time;
+ current_age = corrected_initial_age + resident_time;
+
+ return apr_time_sec(current_age);
+}
+
+CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h,
+ request_rec *r)
+{
+ apr_int64_t age, maxage_req, maxage_cresp, maxage, smaxage, maxstale;
+ apr_int64_t minfresh;
+ const char *cc_cresp, *cc_req;
+ const char *pragma;
+ const char *agestr = NULL;
+ const char *expstr = NULL;
+ char *val;
+ apr_time_t age_c = 0;
+ cache_info *info = &(h->cache_obj->info);
+ cache_server_conf *conf =
+ (cache_server_conf *)ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ /*
+ * We now want to check if our cached data is still fresh. This depends
+ * on a few things, in this order:
+ *
+ * - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache. no-cache in
+ * either the request or the cached response means that we must
+ * revalidate the request unconditionally, overriding any expiration
+ * mechanism. It's equivalent to max-age=0,must-revalidate.
+ *
+ * - RFC2616 14.32 Pragma: no-cache This is treated the same as
+ * Cache-Control: no-cache.
+ *
+ * - RFC2616 14.9.3 Cache-Control: max-stale, must-revalidate,
+ * proxy-revalidate if the max-stale request header exists, modify the
+ * stale calculations below so that an object can be at most <max-stale>
+ * seconds stale before we request a revalidation, _UNLESS_ a
+ * must-revalidate or proxy-revalidate cached response header exists to
+ * stop us doing this.
+ *
+ * - RFC2616 14.9.3 Cache-Control: s-maxage the origin server specifies the
+ * maximum age an object can be before it is considered stale. This
+ * directive has the effect of proxy|must revalidate, which in turn means
+ * simple ignore any max-stale setting.
+ *
+ * - RFC2616 14.9.4 Cache-Control: max-age this header can appear in both
+ * requests and responses. If both are specified, the smaller of the two
+ * takes priority.
+ *
+ * - RFC2616 14.21 Expires: if this request header exists in the cached
+ * entity, and it's value is in the past, it has expired.
+ *
+ */
+
+ /* This value comes from the client's initial request. */
+ cc_req = apr_table_get(r->headers_in, "Cache-Control");
+ pragma = apr_table_get(r->headers_in, "Pragma");
+
+ if (ap_cache_liststr(NULL, pragma, "no-cache", NULL)
+ || ap_cache_liststr(NULL, cc_req, "no-cache", NULL)) {
+
+ if (!conf->ignorecachecontrol) {
+ /* Treat as stale, causing revalidation */
+ return 0;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
+ "Incoming request is asking for a uncached version of "
+ "%s, but we know better and are ignoring it",
+ r->unparsed_uri);
+ }
+
+ /* These come from the cached entity. */
+ cc_cresp = apr_table_get(h->resp_hdrs, "Cache-Control");
+ expstr = apr_table_get(h->resp_hdrs, "Expires");
+
+ if ((agestr = apr_table_get(h->resp_hdrs, "Age"))) {
+ age_c = apr_atoi64(agestr);
+ }
+
+ /* calculate age of object */
+ age = ap_cache_current_age(info, age_c, r->request_time);
+
+ /* extract s-maxage */
+ if (cc_cresp && ap_cache_liststr(r->pool, cc_cresp, "s-maxage", &val)) {
+ smaxage = apr_atoi64(val);
+ }
+ else {
+ smaxage = -1;
+ }
+
+ /* extract max-age from request */
+ if (!conf->ignorecachecontrol
+ && cc_req && ap_cache_liststr(r->pool, cc_req, "max-age", &val)) {
+ maxage_req = apr_atoi64(val);
+ }
+ else {
+ maxage_req = -1;
+ }
+
+ /* extract max-age from response */
+ if (cc_cresp && ap_cache_liststr(r->pool, cc_cresp, "max-age", &val)) {
+ maxage_cresp = apr_atoi64(val);
+ }
+ else {
+ maxage_cresp = -1;
+ }
+
+ /*
+ * if both maxage request and response, the smaller one takes priority
+ */
+ if (maxage_req == -1) {
+ maxage = maxage_cresp;
+ }
+ else if (maxage_cresp == -1) {
+ maxage = maxage_req;
+ }
+ else {
+ maxage = MIN(maxage_req, maxage_cresp);
+ }
+
+ /* extract max-stale */
+ if (cc_req && ap_cache_liststr(r->pool, cc_req, "max-stale", &val)) {
+ maxstale = apr_atoi64(val);
+ }
+ else {
+ maxstale = 0;
+ }
+
+ /* extract min-fresh */
+ if (!conf->ignorecachecontrol
+ && cc_req && ap_cache_liststr(r->pool, cc_req, "min-fresh", &val)) {
+ minfresh = apr_atoi64(val);
+ }
+ else {
+ minfresh = 0;
+ }
+
+ /* override maxstale if must-revalidate or proxy-revalidate */
+ if (maxstale && ((cc_cresp &&
+ ap_cache_liststr(NULL, cc_cresp,
+ "must-revalidate", NULL)) ||
+ (cc_cresp &&
+ ap_cache_liststr(NULL, cc_cresp,
+ "proxy-revalidate", NULL)))) {
+ maxstale = 0;
+ }
+
+ /* handle expiration */
+ if (((smaxage != -1) && (age < (smaxage - minfresh))) ||
+ ((maxage != -1) && (age < (maxage + maxstale - minfresh))) ||
+ ((smaxage == -1) && (maxage == -1) &&
+ (info->expire != APR_DATE_BAD) &&
+ (age < (apr_time_sec(info->expire - info->date) + maxstale - minfresh)))) {
+ const char *warn_head;
+
+ warn_head = apr_table_get(h->resp_hdrs, "Warning");
+
+ /* it's fresh darlings... */
+ /* set age header on response */
+ apr_table_set(h->resp_hdrs, "Age",
+ apr_psprintf(r->pool, "%lu", (unsigned long)age));
+
+ /* add warning if maxstale overrode freshness calculation */
+ if (!(((smaxage != -1) && age < smaxage) ||
+ ((maxage != -1) && age < maxage) ||
+ (info->expire != APR_DATE_BAD &&
+ (info->expire - info->date) > age))) {
+ /* make sure we don't stomp on a previous warning */
+ if ((warn_head == NULL) ||
+ ((warn_head != NULL) && (ap_strstr_c(warn_head, "110") == NULL))) {
+ apr_table_merge(h->resp_hdrs, "Warning",
+ "110 Response is stale");
+ }
+ }
+ /*
+ * If none of Expires, Cache-Control: max-age, or Cache-Control:
+ * s-maxage appears in the response, and the respose header age
+ * calculated is more than 24 hours add the warning 113
+ */
+ if ((maxage_cresp == -1) && (smaxage == -1) &&
+ (expstr == NULL) && (age > 86400)) {
+
+ /* Make sure we don't stomp on a previous warning, and don't dup
+ * a 113 marning that is already present. Also, make sure to add
+ * the new warning to the correct *headers_out location.
+ */
+ if ((warn_head == NULL) ||
+ ((warn_head != NULL) && (ap_strstr_c(warn_head, "113") == NULL))) {
+ apr_table_merge(h->resp_hdrs, "Warning",
+ "113 Heuristic expiration");
+ }
+ }
+ return 1; /* Cache object is fresh (enough) */
+ }
+
+ return 0; /* Cache object is stale */
+}
+
+/*
+ * list is a comma-separated list of case-insensitive tokens, with
+ * optional whitespace around the tokens.
+ * The return returns 1 if the token val is found in the list, or 0
+ * otherwise.
+ */
+CACHE_DECLARE(int) ap_cache_liststr(apr_pool_t *p, const char *list,
+ const char *key, char **val)
+{
+ apr_size_t key_len;
+ const char *next;
+
+ if (!list) {
+ return 0;
+ }
+
+ key_len = strlen(key);
+ next = list;
+
+ for (;;) {
+
+ /* skip whitespace and commas to find the start of the next key */
+ while (*next && (apr_isspace(*next) || (*next == ','))) {
+ next++;
+ }
+
+ if (!*next) {
+ return 0;
+ }
+
+ if (!strncasecmp(next, key, key_len)) {
+ /* this field matches the key (though it might just be
+ * a prefix match, so make sure the match is followed
+ * by either a space or an equals sign)
+ */
+ next += key_len;
+ if (!*next || (*next == '=') || apr_isspace(*next) ||
+ (*next == ',')) {
+ /* valid match */
+ if (val) {
+ while (*next && (*next != '=') && (*next != ',')) {
+ next++;
+ }
+ if (*next == '=') {
+ next++;
+ while (*next && apr_isspace(*next )) {
+ next++;
+ }
+ if (!*next) {
+ *val = NULL;
+ }
+ else {
+ const char *val_start = next;
+ while (*next && !apr_isspace(*next) &&
+ (*next != ',')) {
+ next++;
+ }
+ *val = apr_pstrmemdup(p, val_start,
+ next - val_start);
+ }
+ }
+ }
+ return 1;
+ }
+ }
+
+ /* skip to the next field */
+ do {
+ next++;
+ if (!*next) {
+ return 0;
+ }
+ } while (*next != ',');
+ }
+}
+
+/* return each comma separated token, one at a time */
+CACHE_DECLARE(const char *)ap_cache_tokstr(apr_pool_t *p, const char *list,
+ const char **str)
+{
+ apr_size_t i;
+ const char *s;
+
+ s = ap_strchr_c(list, ',');
+ if (s != NULL) {
+ i = s - list;
+ do
+ s++;
+ while (apr_isspace(*s))
+ ; /* noop */
+ }
+ else
+ i = strlen(list);
+
+ while (i > 0 && apr_isspace(list[i - 1]))
+ i--;
+
+ *str = s;
+ if (i)
+ return apr_pstrndup(p, list, i);
+ else
+ return NULL;
+}
+
+/*
+ * Converts apr_time_t expressed as hex digits to
+ * a true apr_time_t.
+ */
+CACHE_DECLARE(apr_time_t) ap_cache_hex2usec(const char *x)
+{
+ int i, ch;
+ apr_time_t j;
+ for (i = 0, j = 0; i < sizeof(j) * 2; i++) {
+ ch = x[i];
+ j <<= 4;
+ if (apr_isdigit(ch))
+ j |= ch - '0';
+ else if (apr_isupper(ch))
+ j |= ch - ('A' - 10);
+ else
+ j |= ch - ('a' - 10);
+ }
+ return j;
+}
+
+/*
+ * Converts apr_time_t to apr_time_t expressed as hex digits.
+ */
+CACHE_DECLARE(void) ap_cache_usec2hex(apr_time_t j, char *y)
+{
+ int i, ch;
+
+ for (i = (sizeof(j) * 2)-1; i >= 0; i--) {
+ ch = (int)(j & 0xF);
+ j >>= 4;
+ if (ch >= 10)
+ y[i] = ch + ('A' - 10);
+ else
+ y[i] = ch + '0';
+ }
+ y[sizeof(j) * 2] = '\0';
+}
+
+static void cache_hash(const char *it, char *val, int ndepth, int nlength)
+{
+ apr_md5_ctx_t context;
+ unsigned char digest[16];
+ char tmp[22];
+ int i, k, d;
+ unsigned int x;
+ static const char enc_table[64] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@";
+
+ apr_md5_init(&context);
+ apr_md5_update(&context, (const unsigned char *) it, strlen(it));
+ apr_md5_final(digest, &context);
+
+ /* encode 128 bits as 22 characters, using a modified uuencoding
+ * the encoding is 3 bytes -> 4 characters* i.e. 128 bits is
+ * 5 x 3 bytes + 1 byte -> 5 * 4 characters + 2 characters
+ */
+ for (i = 0, k = 0; i < 15; i += 3) {
+ x = (digest[i] << 16) | (digest[i + 1] << 8) | digest[i + 2];
+ tmp[k++] = enc_table[x >> 18];
+ tmp[k++] = enc_table[(x >> 12) & 0x3f];
+ tmp[k++] = enc_table[(x >> 6) & 0x3f];
+ tmp[k++] = enc_table[x & 0x3f];
+ }
+
+ /* one byte left */
+ x = digest[15];
+ tmp[k++] = enc_table[x >> 2]; /* use up 6 bits */
+ tmp[k++] = enc_table[(x << 4) & 0x3f];
+
+ /* now split into directory levels */
+ for (i = k = d = 0; d < ndepth; ++d) {
+ memcpy(&val[i], &tmp[k], nlength);
+ k += nlength;
+ val[i + nlength] = '/';
+ i += nlength + 1;
+ }
+ memcpy(&val[i], &tmp[k], 22 - k);
+ val[i + 22 - k] = '\0';
+}
+
+CACHE_DECLARE(char *)ap_cache_generate_name(apr_pool_t *p, int dirlevels,
+ int dirlength, const char *name)
+{
+ char hashfile[66];
+ cache_hash(name, hashfile, dirlevels, dirlength);
+ return apr_pstrdup(p, hashfile);
+}
+
+/* Create a new table consisting of those elements from an input
+ * headers table that are allowed to be stored in a cache.
+ */
+CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_hdrs_out(apr_pool_t *pool,
+ apr_table_t *t,
+ server_rec *s)
+{
+ cache_server_conf *conf;
+ char **header;
+ int i;
+
+ /* Make a copy of the headers, and remove from
+ * the copy any hop-by-hop headers, as defined in Section
+ * 13.5.1 of RFC 2616
+ */
+ apr_table_t *headers_out;
+ headers_out = apr_table_copy(pool, t);
+ apr_table_unset(headers_out, "Connection");
+ apr_table_unset(headers_out, "Keep-Alive");
+ apr_table_unset(headers_out, "Proxy-Authenticate");
+ apr_table_unset(headers_out, "Proxy-Authorization");
+ apr_table_unset(headers_out, "TE");
+ apr_table_unset(headers_out, "Trailers");
+ apr_table_unset(headers_out, "Transfer-Encoding");
+ apr_table_unset(headers_out, "Upgrade");
+
+ conf = (cache_server_conf *)ap_get_module_config(s->module_config,
+ &cache_module);
+ /* Remove the user defined headers set with CacheIgnoreHeaders.
+ * This may break RFC 2616 compliance on behalf of the administrator.
+ */
+ header = (char **)conf->ignore_headers->elts;
+ for (i = 0; i < conf->ignore_headers->nelts; i++) {
+ apr_table_unset(headers_out, header[i]);
+ }
+ return headers_out;
+}
diff --git a/modules/cache/config.m4 b/modules/cache/config.m4
new file mode 100644
index 00000000..9572e06d
--- /dev/null
+++ b/modules/cache/config.m4
@@ -0,0 +1,26 @@
+dnl modules enabled in this directory by default
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(cache)
+
+APACHE_MODULE(file_cache, File cache, , , no)
+
+dnl # list of object files for mod_cache
+cache_objs="dnl
+mod_cache.lo dnl
+cache_storage.lo dnl
+cache_util.lo dnl
+"
+dnl # list of object files for mod_mem_cache
+mem_cache_objs="dnl
+mod_mem_cache.lo dnl
+cache_cache.lo dnl
+cache_pqueue.lo dnl
+cache_hash.lo dnl
+"
+APACHE_MODULE(cache, dynamic file caching, $cache_objs, , no)
+APACHE_MODULE(disk_cache, disk caching module, , , no)
+APACHE_MODULE(mem_cache, memory caching module, $mem_cache_objs, , no)
+
+APACHE_MODPATH_FINISH
diff --git a/modules/cache/mod_cache.c b/modules/cache/mod_cache.c
new file mode 100644
index 00000000..e492fea5
--- /dev/null
+++ b/modules/cache/mod_cache.c
@@ -0,0 +1,1249 @@
+/* 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.
+ */
+
+#define CORE_PRIVATE
+
+#include "mod_cache.h"
+
+module AP_MODULE_DECLARE_DATA cache_module;
+APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key;
+
+/* -------------------------------------------------------------- */
+
+
+/* Handles for cache filters, resolved at startup to eliminate
+ * a name-to-function mapping on each request
+ */
+static ap_filter_rec_t *cache_save_filter_handle;
+static ap_filter_rec_t *cache_save_subreq_filter_handle;
+static ap_filter_rec_t *cache_out_filter_handle;
+static ap_filter_rec_t *cache_out_subreq_filter_handle;
+static ap_filter_rec_t *cache_remove_url_filter_handle;
+
+/*
+ * CACHE handler
+ * -------------
+ *
+ * Can we deliver this request from the cache?
+ * If yes:
+ * deliver the content by installing the CACHE_OUT filter.
+ * If no:
+ * check whether we're allowed to try cache it
+ * If yes:
+ * add CACHE_SAVE filter
+ * If No:
+ * oh well.
+ */
+
+static int cache_url_handler(request_rec *r, int lookup)
+{
+ apr_status_t rv;
+ const char *auth;
+ cache_provider_list *providers;
+ cache_request_rec *cache;
+ cache_server_conf *conf;
+ apr_bucket_brigade *out;
+
+ /* Delay initialization until we know we are handling a GET */
+ if (r->method_number != M_GET) {
+ return DECLINED;
+ }
+
+ conf = (cache_server_conf *) ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ /*
+ * Which cache module (if any) should handle this request?
+ */
+ if (!(providers = ap_cache_get_providers(r, conf, r->parsed_uri))) {
+ return DECLINED;
+ }
+
+ /* make space for the per request config */
+ cache = (cache_request_rec *) ap_get_module_config(r->request_config,
+ &cache_module);
+ if (!cache) {
+ cache = apr_pcalloc(r->pool, sizeof(cache_request_rec));
+ ap_set_module_config(r->request_config, &cache_module, cache);
+ }
+
+ /* save away the possible providers */
+ cache->providers = providers;
+
+ /*
+ * Are we allowed to serve cached info at all?
+ */
+
+ /* find certain cache controlling headers */
+ auth = apr_table_get(r->headers_in, "Authorization");
+
+ /* First things first - does the request allow us to return
+ * cached information at all? If not, just decline the request.
+ */
+ if (auth) {
+ return DECLINED;
+ }
+
+ /*
+ * Try to serve this request from the cache.
+ *
+ * If no existing cache file (DECLINED)
+ * add cache_save filter
+ * If cached file (OK)
+ * clear filter stack
+ * add cache_out filter
+ * return OK
+ */
+ rv = cache_select(r);
+ if (rv != OK) {
+ if (rv == DECLINED) {
+ if (!lookup) {
+
+ /*
+ * Add cache_save filter to cache this request. Choose
+ * the correct filter by checking if we are a subrequest
+ * or not.
+ */
+ if (r->main) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r->server,
+ "Adding CACHE_SAVE_SUBREQ filter for %s",
+ r->uri);
+ ap_add_output_filter_handle(cache_save_subreq_filter_handle,
+ NULL, r, r->connection);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r->server, "Adding CACHE_SAVE filter for %s",
+ r->uri);
+ ap_add_output_filter_handle(cache_save_filter_handle,
+ NULL, r, r->connection);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
+ "Adding CACHE_REMOVE_URL filter for %s",
+ r->uri);
+
+ /* Add cache_remove_url filter to this request to remove a
+ * stale cache entry if needed. Also put the current cache
+ * request rec in the filter context, as the request that
+ * is available later during running the filter maybe
+ * different due to an internal redirect.
+ */
+ cache->remove_url_filter =
+ ap_add_output_filter_handle(cache_remove_url_filter_handle,
+ cache, r, r->connection);
+ }
+ else {
+ if (cache->stale_headers) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r->server, "Restoring request headers for %s",
+ r->uri);
+
+ r->headers_in = cache->stale_headers;
+ }
+
+ /* Delete our per-request configuration. */
+ ap_set_module_config(r->request_config, &cache_module, NULL);
+ }
+ }
+ else {
+ /* error */
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
+ "cache: error returned while checking for cached "
+ "file by %s cache", cache->provider_name);
+ }
+ return DECLINED;
+ }
+
+ /* if we are a lookup, we are exiting soon one way or another; Restore
+ * the headers. */
+ if (lookup) {
+ if (cache->stale_headers) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
+ "Restoring request headers.");
+ r->headers_in = cache->stale_headers;
+ }
+
+ /* Delete our per-request configuration. */
+ ap_set_module_config(r->request_config, &cache_module, NULL);
+ }
+
+ rv = ap_meets_conditions(r);
+ if (rv != OK) {
+ /* If we are a lookup, we have to return DECLINED as we have no
+ * way of knowing if we will be able to serve the content.
+ */
+ if (lookup) {
+ return DECLINED;
+ }
+
+ /* Return cached status. */
+ return rv;
+ }
+
+ /* If we're a lookup, we can exit now instead of serving the content. */
+ if (lookup) {
+ return OK;
+ }
+
+ /* Serve up the content */
+
+ /* We are in the quick handler hook, which means that no output
+ * filters have been set. So lets run the insert_filter hook.
+ */
+ ap_run_insert_filter(r);
+
+ /*
+ * Add cache_out filter to serve this request. Choose
+ * the correct filter by checking if we are a subrequest
+ * or not.
+ */
+ if (r->main) {
+ ap_add_output_filter_handle(cache_out_subreq_filter_handle, NULL,
+ r, r->connection);
+ }
+ else {
+ ap_add_output_filter_handle(cache_out_filter_handle, NULL,
+ r, r->connection);
+ }
+
+ /* kick off the filter stack */
+ out = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ rv = ap_pass_brigade(r->output_filters, out);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
+ "cache: error returned while trying to return %s "
+ "cached data",
+ cache->provider_name);
+ return rv;
+ }
+
+ return OK;
+}
+
+/*
+ * CACHE_OUT filter
+ * ----------------
+ *
+ * Deliver cached content (headers and body) up the stack.
+ */
+static int cache_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ request_rec *r = f->r;
+ cache_request_rec *cache;
+
+ cache = (cache_request_rec *) ap_get_module_config(r->request_config,
+ &cache_module);
+
+ if (!cache) {
+ /* user likely configured CACHE_OUT manually; they should use mod_cache
+ * configuration to do that */
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "CACHE_OUT enabled unexpectedly");
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
+ "cache: running CACHE_OUT filter");
+
+ /* restore status of cached response */
+ /* XXX: This exposes a bug in mem_cache, since it does not
+ * restore the status into it's handle. */
+ r->status = cache->handle->cache_obj->info.status;
+
+ /* recall_headers() was called in cache_select() */
+ cache->provider->recall_body(cache->handle, r->pool, bb);
+
+ /* This filter is done once it has served up its content */
+ ap_remove_output_filter(f);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
+ "cache: serving %s", r->uri);
+ return ap_pass_brigade(f->next, bb);
+}
+
+
+/*
+ * CACHE_SAVE filter
+ * ---------------
+ *
+ * Decide whether or not this content should be cached.
+ * If we decide no it should not:
+ * remove the filter from the chain
+ * If we decide yes it should:
+ * Have we already started saving the response?
+ * If we have started, pass the data to the storage manager via store_body
+ * Otherwise:
+ * Check to see if we *can* save this particular response.
+ * If we can, call cache_create_entity() and save the headers and body
+ * Finally, pass the data to the next filter (the network or whatever)
+ */
+
+static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
+{
+ int rv = !OK;
+ int date_in_errhdr = 0;
+ request_rec *r = f->r;
+ cache_request_rec *cache;
+ cache_server_conf *conf;
+ const char *cc_out, *cl;
+ const char *exps, *lastmods, *dates, *etag;
+ apr_time_t exp, date, lastmod, now;
+ apr_off_t size;
+ cache_info *info = NULL;
+ char *reason;
+ apr_pool_t *p;
+
+ conf = (cache_server_conf *) ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ /* Setup cache_request_rec */
+ cache = (cache_request_rec *) ap_get_module_config(r->request_config,
+ &cache_module);
+ if (!cache) {
+ /* user likely configured CACHE_SAVE manually; they should really use
+ * mod_cache configuration to do that
+ */
+ cache = apr_pcalloc(r->pool, sizeof(cache_request_rec));
+ ap_set_module_config(r->request_config, &cache_module, cache);
+ }
+
+ reason = NULL;
+ p = r->pool;
+ /*
+ * Pass Data to Cache
+ * ------------------
+ * This section passes the brigades into the cache modules, but only
+ * if the setup section (see below) is complete.
+ */
+ if (cache->block_response) {
+ /* We've already sent down the response and EOS. So, ignore
+ * whatever comes now.
+ */
+ return APR_SUCCESS;
+ }
+
+ /* have we already run the cachability check and set up the
+ * cached file handle?
+ */
+ if (cache->in_checked) {
+ /* pass the brigades into the cache, then pass them
+ * up the filter stack
+ */
+ rv = cache->provider->store_body(cache->handle, r, in);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
+ "cache: Cache provider's store_body failed!");
+ ap_remove_output_filter(f);
+ }
+ return ap_pass_brigade(f->next, in);
+ }
+
+ /*
+ * Setup Data in Cache
+ * -------------------
+ * This section opens the cache entity and sets various caching
+ * parameters, and decides whether this URL should be cached at
+ * all. This section is* run before the above section.
+ */
+
+ /* read expiry date; if a bad date, then leave it so the client can
+ * read it
+ */
+ exps = apr_table_get(r->err_headers_out, "Expires");
+ if (exps == NULL) {
+ exps = apr_table_get(r->headers_out, "Expires");
+ }
+ if (exps != NULL) {
+ if (APR_DATE_BAD == (exp = apr_date_parse_http(exps))) {
+ exps = NULL;
+ }
+ }
+ else {
+ exp = APR_DATE_BAD;
+ }
+
+ /* read the last-modified date; if the date is bad, then delete it */
+ lastmods = apr_table_get(r->err_headers_out, "Last-Modified");
+ if (lastmods == NULL) {
+ lastmods = apr_table_get(r->headers_out, "Last-Modified");
+ }
+ if (lastmods != NULL) {
+ lastmod = apr_date_parse_http(lastmods);
+ if (lastmod == APR_DATE_BAD) {
+ lastmods = NULL;
+ }
+ }
+ else {
+ lastmod = APR_DATE_BAD;
+ }
+
+ /* read the etag and cache-control from the entity */
+ etag = apr_table_get(r->err_headers_out, "Etag");
+ if (etag == NULL) {
+ etag = apr_table_get(r->headers_out, "Etag");
+ }
+ cc_out = apr_table_get(r->err_headers_out, "Cache-Control");
+ if (cc_out == NULL) {
+ cc_out = apr_table_get(r->headers_out, "Cache-Control");
+ }
+
+ /*
+ * what responses should we not cache?
+ *
+ * At this point we decide based on the response headers whether it
+ * is appropriate _NOT_ to cache the data from the server. There are
+ * a whole lot of conditions that prevent us from caching this data.
+ * They are tested here one by one to be clear and unambiguous.
+ */
+ if (r->status != HTTP_OK && r->status != HTTP_NON_AUTHORITATIVE
+ && r->status != HTTP_MULTIPLE_CHOICES
+ && r->status != HTTP_MOVED_PERMANENTLY
+ && r->status != HTTP_NOT_MODIFIED) {
+ /* RFC2616 13.4 we are allowed to cache 200, 203, 206, 300, 301 or 410
+ * We don't cache 206, because we don't (yet) cache partial responses.
+ * We include 304 Not Modified here too as this is the origin server
+ * telling us to serve the cached copy.
+ */
+ reason = apr_psprintf(p, "Response status %d", r->status);
+ }
+ else if (exps != NULL && exp == APR_DATE_BAD) {
+ /* if a broken Expires header is present, don't cache it */
+ reason = apr_pstrcat(p, "Broken expires header: ", exps, NULL);
+ }
+ else if (r->args && exps == NULL) {
+ /* if query string present but no expiration time, don't cache it
+ * (RFC 2616/13.9)
+ */
+ reason = "Query string present but no expires header";
+ }
+ else if (r->status == HTTP_NOT_MODIFIED &&
+ !cache->handle && !cache->stale_handle) {
+ /* if the server said 304 Not Modified but we have no cache
+ * file - pass this untouched to the user agent, it's not for us.
+ */
+ reason = "HTTP Status 304 Not Modified";
+ }
+ else if (r->status == HTTP_OK && lastmods == NULL && etag == NULL
+ && (exps == NULL) && (conf->no_last_mod_ignore ==0)) {
+ /* 200 OK response from HTTP/1.0 and up without Last-Modified,
+ * Etag, or Expires headers.
+ */
+ /* Note: mod-include clears last_modified/expires/etags - this
+ * is why we have an optional function for a key-gen ;-)
+ */
+ reason = "No Last-Modified, Etag, or Expires headers";
+ }
+ else if (r->header_only) {
+ /* HEAD requests */
+ reason = "HTTP HEAD request";
+ }
+ else if (!conf->store_nostore &&
+ ap_cache_liststr(NULL, cc_out, "no-store", NULL)) {
+ /* RFC2616 14.9.2 Cache-Control: no-store response
+ * indicating do not cache, or stop now if you are
+ * trying to cache it.
+ */
+ /* FIXME: The Cache-Control: no-store could have come in on a 304,
+ * FIXME: while the original request wasn't conditional. IOW, we
+ * FIXME: made the the request conditional earlier to revalidate
+ * FIXME: our cached response.
+ */
+ reason = "Cache-Control: no-store present";
+ }
+ else if (!conf->store_private &&
+ ap_cache_liststr(NULL, cc_out, "private", NULL)) {
+ /* RFC2616 14.9.1 Cache-Control: private response
+ * this object is marked for this user's eyes only. Behave
+ * as a tunnel.
+ */
+ /* FIXME: See above (no-store) */
+ reason = "Cache-Control: private present";
+ }
+ else if (apr_table_get(r->headers_in, "Authorization") != NULL
+ && !(ap_cache_liststr(NULL, cc_out, "s-maxage", NULL)
+ || ap_cache_liststr(NULL, cc_out, "must-revalidate", NULL)
+ || ap_cache_liststr(NULL, cc_out, "public", NULL))) {
+ /* RFC2616 14.8 Authorisation:
+ * if authorisation is included in the request, we don't cache,
+ * but we can cache if the following exceptions are true:
+ * 1) If Cache-Control: s-maxage is included
+ * 2) If Cache-Control: must-revalidate is included
+ * 3) If Cache-Control: public is included
+ */
+ reason = "Authorization required";
+ }
+ else if (ap_cache_liststr(NULL,
+ apr_table_get(r->headers_out, "Vary"),
+ "*", NULL)) {
+ reason = "Vary header contains '*'";
+ }
+ else if (r->no_cache) {
+ /* or we've been asked not to cache it above */
+ reason = "r->no_cache present";
+ }
+
+ if (reason) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "cache: %s not cached. Reason: %s", r->unparsed_uri,
+ reason);
+
+ /* remove this filter from the chain */
+ ap_remove_output_filter(f);
+
+ /* ship the data up the stack */
+ return ap_pass_brigade(f->next, in);
+ }
+
+ /* Make it so that we don't execute this path again. */
+ cache->in_checked = 1;
+
+ /* Set the content length if known.
+ */
+ cl = apr_table_get(r->err_headers_out, "Content-Length");
+ if (cl == NULL) {
+ cl = apr_table_get(r->headers_out, "Content-Length");
+ }
+ if (cl) {
+ char *errp;
+ if (apr_strtoff(&size, cl, &errp, 10) || *errp || size < 0) {
+ cl = NULL; /* parse error, see next 'if' block */
+ }
+ }
+
+ if (!cl) {
+ /* if we don't get the content-length, see if we have all the
+ * buckets and use their length to calculate the size
+ */
+ apr_bucket *e;
+ int all_buckets_here=0;
+ int unresolved_length = 0;
+ size=0;
+ for (e = APR_BRIGADE_FIRST(in);
+ e != APR_BRIGADE_SENTINEL(in);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (APR_BUCKET_IS_EOS(e)) {
+ all_buckets_here=1;
+ break;
+ }
+ if (APR_BUCKET_IS_FLUSH(e)) {
+ unresolved_length = 1;
+ continue;
+ }
+ if (e->length == (apr_size_t)-1) {
+ break;
+ }
+ size += e->length;
+ }
+ if (!all_buckets_here) {
+ size = -1;
+ }
+ }
+
+ /* It's safe to cache the response.
+ *
+ * There are two possiblities at this point:
+ * - cache->handle == NULL. In this case there is no previously
+ * cached entity anywhere on the system. We must create a brand
+ * new entity and store the response in it.
+ * - cache->stale_handle != NULL. In this case there is a stale
+ * entity in the system which needs to be replaced by new
+ * content (unless the result was 304 Not Modified, which means
+ * the cached entity is actually fresh, and we should update
+ * the headers).
+ */
+
+ /* Did we have a stale cache entry that really is stale? */
+ if (cache->stale_handle) {
+ if (r->status == HTTP_NOT_MODIFIED) {
+ /* Oh, hey. It isn't that stale! Yay! */
+ cache->handle = cache->stale_handle;
+ info = &cache->handle->cache_obj->info;
+ rv = OK;
+ }
+ else {
+ /* Oh, well. Toss it. */
+ cache->provider->remove_entity(cache->stale_handle);
+ /* Treat the request as if it wasn't conditional. */
+ cache->stale_handle = NULL;
+ }
+ }
+
+ /* no cache handle, create a new entity */
+ if (!cache->handle) {
+ rv = cache_create_entity(r, size);
+ info = apr_pcalloc(r->pool, sizeof(cache_info));
+ /* We only set info->status upon the initial creation. */
+ info->status = r->status;
+ }
+
+ if (rv != OK) {
+ /* Caching layer declined the opportunity to cache the response */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, in);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "cache: Caching url: %s", r->unparsed_uri);
+
+ /* We are actually caching this response. So it does not
+ * make sense to remove this entity any more.
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "cache: Removing CACHE_REMOVE_URL filter.");
+ ap_remove_output_filter(cache->remove_url_filter);
+
+ /*
+ * We now want to update the cache file header information with
+ * the new date, last modified, expire and content length and write
+ * it away to our cache file. First, we determine these values from
+ * the response, using heuristics if appropriate.
+ *
+ * In addition, we make HTTP/1.1 age calculations and write them away
+ * too.
+ */
+
+ /* Read the date. Generate one if one is not supplied */
+ dates = apr_table_get(r->err_headers_out, "Date");
+ if (dates != NULL) {
+ date_in_errhdr = 1;
+ }
+ else {
+ dates = apr_table_get(r->headers_out, "Date");
+ }
+ if (dates != NULL) {
+ info->date = apr_date_parse_http(dates);
+ }
+ else {
+ info->date = APR_DATE_BAD;
+ }
+
+ now = apr_time_now();
+ if (info->date == APR_DATE_BAD) { /* No, or bad date */
+ char *dates;
+ /* no date header (or bad header)! */
+ /* add one; N.B. use the time _now_ rather than when we were checking
+ * the cache
+ */
+ if (date_in_errhdr == 1) {
+ apr_table_unset(r->err_headers_out, "Date");
+ }
+ date = now;
+ dates = apr_pcalloc(r->pool, MAX_STRING_LEN);
+ apr_rfc822_date(dates, now);
+ apr_table_set(r->headers_out, "Date", dates);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "cache: Added date header");
+ info->date = date;
+ }
+ else {
+ date = info->date;
+ }
+
+ /* set response_time for HTTP/1.1 age calculations */
+ info->response_time = now;
+
+ /* get the request time */
+ info->request_time = r->request_time;
+
+ /* check last-modified date */
+ if (lastmod != APR_DATE_BAD && lastmod > date) {
+ /* if it's in the future, then replace by date */
+ lastmod = date;
+ lastmods = dates;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ r->server,
+ "cache: Last modified is in the future, "
+ "replacing with now");
+ }
+
+ /* if no expiry date then
+ * if lastmod
+ * expiry date = date + min((date - lastmod) * factor, maxexpire)
+ * else
+ * expire date = date + defaultexpire
+ */
+ if (exp == APR_DATE_BAD) {
+ char expire_hdr[APR_RFC822_DATE_LEN];
+
+ /* if lastmod == date then you get 0*conf->factor which results in
+ * an expiration time of now. This causes some problems with
+ * freshness calculations, so we choose the else path...
+ */
+ if ((lastmod != APR_DATE_BAD) && (lastmod < date)) {
+ apr_time_t x = (apr_time_t) ((date - lastmod) * conf->factor);
+
+ if (x > conf->maxex) {
+ x = conf->maxex;
+ }
+ exp = date + x;
+ apr_rfc822_date(expire_hdr, exp);
+ apr_table_set(r->headers_out, "Expires", expire_hdr);
+ }
+ else {
+ exp = date + conf->defex;
+ apr_rfc822_date(expire_hdr, exp);
+ apr_table_set(r->headers_out, "Expires", expire_hdr);
+ }
+ }
+ info->expire = exp;
+
+ /* We found a stale entry which wasn't really stale. */
+ if (cache->stale_handle) {
+ /* Load in the saved status and clear the status line. */
+ r->status = info->status;
+ r->status_line = NULL;
+
+ /* RFC 2616 10.3.5 states that entity headers are not supposed
+ * to be in the 304 response. Therefore, we need to combine the
+ * response headers with the cached headers *before* we update
+ * the cached headers.
+ *
+ * However, before doing that, we need to first merge in
+ * err_headers_out and we also need to strip any hop-by-hop
+ * headers that might have snuck in.
+ */
+ r->headers_out = apr_table_overlay(r->pool, r->headers_out,
+ r->err_headers_out);
+ r->headers_out = ap_cache_cacheable_hdrs_out(r->pool, r->headers_out,
+ r->server);
+ apr_table_clear(r->err_headers_out);
+
+ /* Merge in our cached headers. However, keep any updated values. */
+ ap_cache_accept_headers(cache->handle, r, 1);
+ }
+
+ /* Write away header information to cache. It is possible that we are
+ * trying to update headers for an entity which has already been cached.
+ *
+ * This may fail, due to an unwritable cache area. E.g. filesystem full,
+ * permissions problems or a read-only (re)mount. This must be handled
+ * later.
+ */
+ rv = cache->provider->store_headers(cache->handle, r, info);
+
+ /* Did we just update the cached headers on a revalidated response?
+ *
+ * If so, we can now decide what to serve to the client. This is done in
+ * the same way as with a regular response, but conditions are now checked
+ * against the cached or merged response headers.
+ */
+ if (cache->stale_handle) {
+ apr_bucket_brigade *bb;
+ apr_bucket *bkt;
+ int status;
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ /* Restore the original request headers and see if we need to
+ * return anything else than the cached response (ie. the original
+ * request was conditional).
+ */
+ r->headers_in = cache->stale_headers;
+ status = ap_meets_conditions(r);
+ if (status != OK) {
+ r->status = status;
+
+ bkt = apr_bucket_flush_create(bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, bkt);
+ }
+ else {
+ cache->provider->recall_body(cache->handle, r->pool, bb);
+ }
+
+ cache->block_response = 1;
+
+ /* Before returning we need to handle the possible case of an
+ * unwritable cache. Rather than leaving the entity in the cache
+ * and having it constantly re-validated, now that we have recalled
+ * the body it is safe to try and remove the url from the cache.
+ */
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
+ "cache: updating headers with store_headers failed. "
+ "Removing cached url.");
+
+ rv = cache->provider->remove_url(cache->stale_handle, r->pool);
+ if (rv != OK) {
+ /* Probably a mod_disk_cache cache area has been (re)mounted
+ * read-only, or that there is a permissions problem.
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
+ "cache: attempt to remove url from cache unsuccessful.");
+ }
+ }
+
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ if(rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
+ "cache: store_headers failed");
+ ap_remove_output_filter(f);
+
+ return ap_pass_brigade(f->next, in);
+ }
+
+ rv = cache->provider->store_body(cache->handle, r, in);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
+ "cache: store_body failed");
+ ap_remove_output_filter(f);
+ }
+
+ return ap_pass_brigade(f->next, in);
+}
+
+/*
+ * CACHE_REMOVE_URL filter
+ * ---------------
+ *
+ * This filter gets added in the quick handler every time the CACHE_SAVE filter
+ * gets inserted. Its purpose is to remove a confirmed stale cache entry from
+ * the cache.
+ *
+ * CACHE_REMOVE_URL has to be a protocol filter to ensure that is run even if
+ * the response is a canned error message, which removes the content filters
+ * and thus the CACHE_SAVE filter from the chain.
+ *
+ * CACHE_REMOVE_URL expects cache request rec within its context because the
+ * request this filter runs on can be different from the one whose cache entry
+ * should be removed, due to internal redirects.
+ *
+ * Note that CACHE_SAVE_URL (as a content-set filter, hence run before the
+ * protocol filters) will remove this filter if it decides to cache the file.
+ * Therefore, if this filter is left in, it must mean we need to toss any
+ * existing files.
+ */
+static int cache_remove_url_filter(ap_filter_t *f, apr_bucket_brigade *in)
+{
+ request_rec *r = f->r;
+ cache_request_rec *cache;
+
+ /* Setup cache_request_rec */
+ cache = (cache_request_rec *) f->ctx;
+
+ if (!cache) {
+ /* user likely configured CACHE_REMOVE_URL manually; they should really
+ * use mod_cache configuration to do that. So:
+ * 1. Remove ourselves
+ * 2. Do nothing and bail out
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "cache: CACHE_REMOVE_URL enabled unexpectedly");
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, in);
+ }
+ /* Now remove this cache entry from the cache */
+ cache_remove_url(cache, r->pool);
+
+ /* remove ourselves */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, in);
+}
+
+/* -------------------------------------------------------------- */
+/* Setup configurable data */
+
+static void * create_cache_config(apr_pool_t *p, server_rec *s)
+{
+ cache_server_conf *ps = apr_pcalloc(p, sizeof(cache_server_conf));
+
+ /* array of URL prefixes for which caching is enabled */
+ ps->cacheenable = apr_array_make(p, 10, sizeof(struct cache_enable));
+ /* array of URL prefixes for which caching is disabled */
+ ps->cachedisable = apr_array_make(p, 10, sizeof(struct cache_disable));
+ /* maximum time to cache a document */
+ ps->maxex = DEFAULT_CACHE_MAXEXPIRE;
+ ps->maxex_set = 0;
+ /* default time to cache a document */
+ ps->defex = DEFAULT_CACHE_EXPIRE;
+ ps->defex_set = 0;
+ /* factor used to estimate Expires date from LastModified date */
+ ps->factor = DEFAULT_CACHE_LMFACTOR;
+ ps->factor_set = 0;
+ ps->no_last_mod_ignore_set = 0;
+ ps->no_last_mod_ignore = 0;
+ ps->ignorecachecontrol = 0;
+ ps->ignorecachecontrol_set = 0;
+ ps->store_private = 0;
+ ps->store_private_set = 0;
+ ps->store_nostore = 0;
+ ps->store_nostore_set = 0;
+ /* array of headers that should not be stored in cache */
+ ps->ignore_headers = apr_array_make(p, 10, sizeof(char *));
+ ps->ignore_headers_set = CACHE_IGNORE_HEADERS_UNSET;
+ return ps;
+}
+
+static void * merge_cache_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ cache_server_conf *ps = apr_pcalloc(p, sizeof(cache_server_conf));
+ cache_server_conf *base = (cache_server_conf *) basev;
+ cache_server_conf *overrides = (cache_server_conf *) overridesv;
+
+ /* array of URL prefixes for which caching is disabled */
+ ps->cachedisable = apr_array_append(p,
+ base->cachedisable,
+ overrides->cachedisable);
+ /* array of URL prefixes for which caching is enabled */
+ ps->cacheenable = apr_array_append(p,
+ base->cacheenable,
+ overrides->cacheenable);
+ /* maximum time to cache a document */
+ ps->maxex = (overrides->maxex_set == 0) ? base->maxex : overrides->maxex;
+ /* default time to cache a document */
+ ps->defex = (overrides->defex_set == 0) ? base->defex : overrides->defex;
+ /* factor used to estimate Expires date from LastModified date */
+ ps->factor =
+ (overrides->factor_set == 0) ? base->factor : overrides->factor;
+
+ ps->no_last_mod_ignore =
+ (overrides->no_last_mod_ignore_set == 0)
+ ? base->no_last_mod_ignore
+ : overrides->no_last_mod_ignore;
+ ps->ignorecachecontrol =
+ (overrides->ignorecachecontrol_set == 0)
+ ? base->ignorecachecontrol
+ : overrides->ignorecachecontrol;
+ ps->store_private =
+ (overrides->store_private_set == 0)
+ ? base->store_private
+ : overrides->store_private;
+ ps->store_nostore =
+ (overrides->store_nostore_set == 0)
+ ? base->store_nostore
+ : overrides->store_nostore;
+ ps->ignore_headers =
+ (overrides->ignore_headers_set == CACHE_IGNORE_HEADERS_UNSET)
+ ? base->ignore_headers
+ : overrides->ignore_headers;
+ return ps;
+}
+static const char *set_cache_ignore_no_last_mod(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_server_conf *conf;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ conf->no_last_mod_ignore = flag;
+ conf->no_last_mod_ignore_set = 1;
+ return NULL;
+
+}
+
+static const char *set_cache_ignore_cachecontrol(cmd_parms *parms,
+ void *dummy, int flag)
+{
+ cache_server_conf *conf;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ conf->ignorecachecontrol = flag;
+ conf->ignorecachecontrol_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_store_private(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_server_conf *conf;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ conf->store_private = flag;
+ conf->store_private_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_store_nostore(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_server_conf *conf;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ conf->store_nostore = flag;
+ conf->store_nostore_set = 1;
+ return NULL;
+}
+
+static const char *add_ignore_header(cmd_parms *parms, void *dummy,
+ const char *header)
+{
+ cache_server_conf *conf;
+ char **new;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ if (!strncasecmp(header, "None", 4)) {
+ /* if header None is listed clear array */
+ conf->ignore_headers->nelts = 0;
+ }
+ else {
+ if ((conf->ignore_headers_set == CACHE_IGNORE_HEADERS_UNSET) ||
+ (conf->ignore_headers->nelts)) {
+ /* Only add header if no "None" has been found in header list
+ * so far.
+ * (When 'None' is passed, IGNORE_HEADERS_SET && nelts == 0.)
+ */
+ new = (char **)apr_array_push(conf->ignore_headers);
+ (*new) = (char *)header;
+ }
+ }
+ conf->ignore_headers_set = CACHE_IGNORE_HEADERS_SET;
+ return NULL;
+}
+
+static const char *add_cache_enable(cmd_parms *parms, void *dummy,
+ const char *type,
+ const char *url)
+{
+ cache_server_conf *conf;
+ struct cache_enable *new;
+
+ if (*type == '/') {
+ return apr_psprintf(parms->pool,
+ "provider (%s) starts with a '/'. Are url and provider switched?",
+ type);
+ }
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ new = apr_array_push(conf->cacheenable);
+ new->type = type;
+ if (apr_uri_parse(parms->pool, url, &(new->url))) {
+ return NULL;
+ }
+ if (new->url.path) {
+ new->pathlen = strlen(new->url.path);
+ } else {
+ new->pathlen = 1;
+ new->url.path = "/";
+ }
+ return NULL;
+}
+
+static const char *add_cache_disable(cmd_parms *parms, void *dummy,
+ const char *url)
+{
+ cache_server_conf *conf;
+ struct cache_disable *new;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ new = apr_array_push(conf->cachedisable);
+ if (apr_uri_parse(parms->pool, url, &(new->url))) {
+ return NULL;
+ }
+ if (new->url.path) {
+ new->pathlen = strlen(new->url.path);
+ } else {
+ new->pathlen = 1;
+ new->url.path = "/";
+ }
+ return NULL;
+}
+
+static const char *set_cache_maxex(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ cache_server_conf *conf;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ conf->maxex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC);
+ conf->maxex_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_defex(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ cache_server_conf *conf;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ conf->defex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC);
+ conf->defex_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_factor(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ cache_server_conf *conf;
+ double val;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ if (sscanf(arg, "%lg", &val) != 1) {
+ return "CacheLastModifiedFactor value must be a float";
+ }
+ conf->factor = val;
+ conf->factor_set = 1;
+ return NULL;
+}
+
+static int cache_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ /* This is the means by which unusual (non-unix) os's may find alternate
+ * means to run a given command (e.g. shebang/registry parsing on Win32)
+ */
+ cache_generate_key = APR_RETRIEVE_OPTIONAL_FN(ap_cache_generate_key);
+ if (!cache_generate_key) {
+ cache_generate_key = cache_generate_key_default;
+ }
+ return OK;
+}
+
+
+static const command_rec cache_cmds[] =
+{
+ /* XXX
+ * Consider a new config directive that enables loading specific cache
+ * implememtations (like mod_cache_mem, mod_cache_file, etc.).
+ * Rather than using a LoadModule directive, admin would use something
+ * like CacheModule mem_cache_module | file_cache_module, etc,
+ * which would cause the approprpriate cache module to be loaded.
+ * This is more intuitive that requiring a LoadModule directive.
+ */
+
+ AP_INIT_TAKE2("CacheEnable", add_cache_enable, NULL, RSRC_CONF,
+ "A cache type and partial URL prefix below which "
+ "caching is enabled"),
+ AP_INIT_TAKE1("CacheDisable", add_cache_disable, NULL, RSRC_CONF,
+ "A partial URL prefix below which caching is disabled"),
+ AP_INIT_TAKE1("CacheMaxExpire", set_cache_maxex, NULL, RSRC_CONF,
+ "The maximum time in seconds to cache a document"),
+ AP_INIT_TAKE1("CacheDefaultExpire", set_cache_defex, NULL, RSRC_CONF,
+ "The default time in seconds to cache a document"),
+ AP_INIT_FLAG("CacheIgnoreNoLastMod", set_cache_ignore_no_last_mod, NULL,
+ RSRC_CONF,
+ "Ignore Responses where there is no Last Modified Header"),
+ AP_INIT_FLAG("CacheIgnoreCacheControl", set_cache_ignore_cachecontrol,
+ NULL, RSRC_CONF,
+ "Ignore requests from the client for uncached content"),
+ AP_INIT_FLAG("CacheStorePrivate", set_cache_store_private,
+ NULL, RSRC_CONF,
+ "Ignore 'Cache-Control: private' and store private content"),
+ AP_INIT_FLAG("CacheStoreNoStore", set_cache_store_nostore,
+ NULL, RSRC_CONF,
+ "Ignore 'Cache-Control: no-store' and store sensitive content"),
+ AP_INIT_ITERATE("CacheIgnoreHeaders", add_ignore_header, NULL, RSRC_CONF,
+ "A space separated list of headers that should not be "
+ "stored by the cache"),
+ AP_INIT_TAKE1("CacheLastModifiedFactor", set_cache_factor, NULL, RSRC_CONF,
+ "The factor used to estimate Expires date from "
+ "LastModified date"),
+ {NULL}
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ /* cache initializer */
+ /* cache handler */
+ ap_hook_quick_handler(cache_url_handler, NULL, NULL, APR_HOOK_FIRST);
+ /* cache filters
+ * XXX The cache filters need to run right after the handlers and before
+ * any other filters. Consider creating AP_FTYPE_CACHE for this purpose.
+ *
+ * Depending on the type of request (subrequest / main request) they
+ * need to be run before AP_FTYPE_CONTENT_SET / after AP_FTYPE_CONTENT_SET
+ * filters. Thus create two filter handles for each type:
+ * cache_save_filter_handle / cache_out_filter_handle to be used by
+ * main requests and
+ * cache_save_subreq_filter_handle / cache_out_subreq_filter_handle
+ * to be run by subrequest
+ */
+ /*
+ * CACHE_SAVE must go into the filter chain after a possible DEFLATE
+ * filter to ensure that the compressed content is stored.
+ * Incrementing filter type by 1 ensures his happens.
+ */
+ cache_save_filter_handle =
+ ap_register_output_filter("CACHE_SAVE",
+ cache_save_filter,
+ NULL,
+ AP_FTYPE_CONTENT_SET+1);
+ /*
+ * CACHE_SAVE_SUBREQ must go into the filter chain before SUBREQ_CORE to
+ * handle subrequsts. Decrementing filter type by 1 ensures this
+ * happens.
+ */
+ cache_save_subreq_filter_handle =
+ ap_register_output_filter("CACHE_SAVE_SUBREQ",
+ cache_save_filter,
+ NULL,
+ AP_FTYPE_CONTENT_SET-1);
+ /*
+ * CACHE_OUT must go into the filter chain after a possible DEFLATE
+ * filter to ensure that already compressed cache objects do not
+ * get compressed again. Incrementing filter type by 1 ensures
+ * his happens.
+ */
+ cache_out_filter_handle =
+ ap_register_output_filter("CACHE_OUT",
+ cache_out_filter,
+ NULL,
+ AP_FTYPE_CONTENT_SET+1);
+ /*
+ * CACHE_OUT_SUBREQ must go into the filter chain before SUBREQ_CORE to
+ * handle subrequsts. Decrementing filter type by 1 ensures this
+ * happens.
+ */
+ cache_out_subreq_filter_handle =
+ ap_register_output_filter("CACHE_OUT_SUBREQ",
+ cache_out_filter,
+ NULL,
+ AP_FTYPE_CONTENT_SET-1);
+ /* CACHE_REMOVE_URL has to be a protocol filter to ensure that is
+ * run even if the response is a canned error message, which
+ * removes the content filters.
+ */
+ cache_remove_url_filter_handle =
+ ap_register_output_filter("CACHE_REMOVE_URL",
+ cache_remove_url_filter,
+ NULL,
+ AP_FTYPE_PROTOCOL);
+ ap_hook_post_config(cache_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
+}
+
+module AP_MODULE_DECLARE_DATA cache_module =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ create_cache_config, /* create per-server config structure */
+ merge_cache_config, /* merge per-server config structures */
+ cache_cmds, /* command apr_table_t */
+ register_hooks
+};
diff --git a/modules/cache/mod_cache.dsp b/modules/cache/mod_cache.dsp
new file mode 100644
index 00000000..f53aca4f
--- /dev/null
+++ b/modules/cache/mod_cache.dsp
@@ -0,0 +1,143 @@
+# Microsoft Developer Studio Project File - Name="mod_cache" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_cache - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cache.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cache.mak" CFG="mod_cache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_cache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_cache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_cache - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "MOD_CACHE_EXPORTS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "CACHE_DECLARE_EXPORT" /D "MOD_CACHE_EXPORTS" /Fd"Release\mod_cache_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_cache.so" /d "LONG_NAME=cache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Release/mod_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache.so /opt:ref
+
+!ELSEIF "$(CFG)" == "mod_cache - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "CACHE_DECLARE_EXPORT" /Fd"Debug\mod_cache_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_cache.so" /d "LONG_NAME=cache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache.so
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_cache - Win32 Release"
+# Name "mod_cache - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\cache_cache.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cache_hash.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cache_pqueue.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cache_storage.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cache_util.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_cache.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=.\cache_cache.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\cache_hash.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\cache_pqueue.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_cache.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cache/mod_cache.h b/modules/cache/mod_cache.h
new file mode 100644
index 00000000..eb5c8ced
--- /dev/null
+++ b/modules/cache/mod_cache.h
@@ -0,0 +1,327 @@
+/* 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.
+ */
+
+/**
+ * @file mod_cache.h
+ * @brief Main include file for the Apache Transparent Cache
+ *
+ * @defgroup MOD_CACHE mod_cache
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#ifndef MOD_CACHE_H
+#define MOD_CACHE_H
+
+#define CORE_PRIVATE
+
+#include "apr_hooks.h"
+#include "apr.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_md5.h"
+#include "apr_pools.h"
+#include "apr_strings.h"
+#include "apr_optional.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "ap_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_vhost.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_connection.h"
+#include "util_filter.h"
+#include "apr_date.h"
+#include "apr_uri.h"
+
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#include "apr_atomic.h"
+
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#define MSEC_ONE_DAY ((apr_time_t)(86400*APR_USEC_PER_SEC)) /* one day, in microseconds */
+#define MSEC_ONE_HR ((apr_time_t)(3600*APR_USEC_PER_SEC)) /* one hour, in microseconds */
+#define MSEC_ONE_MIN ((apr_time_t)(60*APR_USEC_PER_SEC)) /* one minute, in microseconds */
+#define MSEC_ONE_SEC ((apr_time_t)(APR_USEC_PER_SEC)) /* one second, in microseconds */
+#define DEFAULT_CACHE_MAXEXPIRE MSEC_ONE_DAY
+#define DEFAULT_CACHE_EXPIRE MSEC_ONE_HR
+#define DEFAULT_CACHE_LMFACTOR (0.1)
+
+/* Create a set of PROXY_DECLARE(type), PROXY_DECLARE_NONSTD(type) and
+ * PROXY_DECLARE_DATA with appropriate export and import tags for the platform
+ */
+#if !defined(WIN32)
+#define CACHE_DECLARE(type) type
+#define CACHE_DECLARE_NONSTD(type) type
+#define CACHE_DECLARE_DATA
+#elif defined(CACHE_DECLARE_STATIC)
+#define CACHE_DECLARE(type) type __stdcall
+#define CACHE_DECLARE_NONSTD(type) type
+#define CACHE_DECLARE_DATA
+#elif defined(CACHE_DECLARE_EXPORT)
+#define CACHE_DECLARE(type) __declspec(dllexport) type __stdcall
+#define CACHE_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define CACHE_DECLARE_DATA __declspec(dllexport)
+#else
+#define CACHE_DECLARE(type) __declspec(dllimport) type __stdcall
+#define CACHE_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define CACHE_DECLARE_DATA __declspec(dllimport)
+#endif
+
+struct cache_enable {
+ apr_uri_t url;
+ const char *type;
+ apr_size_t pathlen;
+};
+
+struct cache_disable {
+ apr_uri_t url;
+ apr_size_t pathlen;
+};
+
+/* static information about the local cache */
+typedef struct {
+ apr_array_header_t *cacheenable; /* URLs to cache */
+ apr_array_header_t *cachedisable; /* URLs not to cache */
+ /* Maximum time to keep cached files in msecs */
+ apr_time_t maxex;
+ int maxex_set;
+ /* default time to keep cached file in msecs */
+ apr_time_t defex;
+ int defex_set;
+ /* factor for estimating expires date */
+ double factor;
+ int factor_set;
+ /** ignore the last-modified header when deciding to cache this request */
+ int no_last_mod_ignore_set;
+ int no_last_mod_ignore;
+ /** ignore client's requests for uncached responses */
+ int ignorecachecontrol;
+ int ignorecachecontrol_set;
+ /** ignore Cache-Control: private header from server */
+ int store_private;
+ int store_private_set;
+ /** ignore Cache-Control: no-store header from client or server */
+ int store_nostore;
+ int store_nostore_set;
+ /** store the headers that should not be stored in the cache */
+ apr_array_header_t *ignore_headers;
+ /* flag if CacheIgnoreHeader has been set */
+ #define CACHE_IGNORE_HEADERS_SET 1
+ #define CACHE_IGNORE_HEADERS_UNSET 0
+ int ignore_headers_set;
+} cache_server_conf;
+
+/* cache info information */
+typedef struct cache_info cache_info;
+struct cache_info {
+ int status;
+ apr_time_t date;
+ apr_time_t expire;
+ apr_time_t request_time;
+ apr_time_t response_time;
+};
+
+/* cache handle information */
+
+/* XXX TODO On the next structure change/MMN bump,
+ * count must become an apr_off_t, representing
+ * the potential size of disk cached objects.
+ * Then dig for
+ * "XXX Bad Temporary Cast - see cache_object_t notes"
+ */
+typedef struct cache_object cache_object_t;
+struct cache_object {
+ const char *key;
+ cache_object_t *next;
+ cache_info info;
+ /* Opaque portion (specific to the implementation) of the cache object */
+ void *vobj;
+ /* FIXME: These are only required for mod_mem_cache. */
+ apr_size_t count; /* Number of body bytes written to the cache so far */
+ int complete;
+ apr_uint32_t refcount; /* refcount and bit flag to cleanup object */
+};
+
+typedef struct cache_handle cache_handle_t;
+struct cache_handle {
+ cache_object_t *cache_obj;
+ apr_table_t *req_hdrs; /* cached request headers */
+ apr_table_t *resp_hdrs; /* cached response headers */
+};
+
+#define CACHE_PROVIDER_GROUP "cache"
+
+typedef struct {
+ int (*remove_entity) (cache_handle_t *h);
+ apr_status_t (*store_headers)(cache_handle_t *h, request_rec *r, cache_info *i);
+ apr_status_t (*store_body)(cache_handle_t *h, request_rec *r, apr_bucket_brigade *b);
+ apr_status_t (*recall_headers) (cache_handle_t *h, request_rec *r);
+ apr_status_t (*recall_body) (cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb);
+ int (*create_entity) (cache_handle_t *h, request_rec *r,
+ const char *urlkey, apr_off_t len);
+ int (*open_entity) (cache_handle_t *h, request_rec *r,
+ const char *urlkey);
+ int (*remove_url) (cache_handle_t *h, apr_pool_t *p);
+} cache_provider;
+
+/* A linked-list of authn providers. */
+typedef struct cache_provider_list cache_provider_list;
+
+struct cache_provider_list {
+ const char *provider_name;
+ const cache_provider *provider;
+ cache_provider_list *next;
+};
+
+/* per request cache information */
+typedef struct {
+ cache_provider_list *providers; /* possible cache providers */
+ const cache_provider *provider; /* current cache provider */
+ const char *provider_name; /* current cache provider name */
+ int fresh; /* is the entitey fresh? */
+ cache_handle_t *handle; /* current cache handle */
+ cache_handle_t *stale_handle; /* stale cache handle */
+ apr_table_t *stale_headers; /* original request headers. */
+ int in_checked; /* CACHE_SAVE must cache the entity */
+ int block_response; /* CACHE_SAVE must block response. */
+ apr_bucket_brigade *saved_brigade; /* copy of partial response */
+ apr_off_t saved_size; /* length of saved_brigade */
+ apr_time_t exp; /* expiration */
+ apr_time_t lastmod; /* last-modified time */
+ cache_info *info; /* current cache info */
+ ap_filter_t *remove_url_filter; /* Enable us to remove the filter */
+} cache_request_rec;
+
+
+/* cache_util.c */
+/* do a HTTP/1.1 age calculation */
+CACHE_DECLARE(apr_time_t) ap_cache_current_age(cache_info *info, const apr_time_t age_value,
+ apr_time_t now);
+
+/**
+ * Check the freshness of the cache object per RFC2616 section 13.2 (Expiration Model)
+ * @param h cache_handle_t
+ * @param r request_rec
+ * @return 0 ==> cache object is stale, 1 ==> cache object is fresh
+ */
+CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h, request_rec *r);
+
+/**
+ * Merge in cached headers into the response
+ * @param h cache_handle_t
+ * @param r request_rec
+ * @param preserve_orig If 1, the values in r->headers_out are preserved.
+ * Otherwise, they are overwritten by the cached value.
+ */
+CACHE_DECLARE(void) ap_cache_accept_headers(cache_handle_t *h, request_rec *r,
+ int preserve_orig);
+
+CACHE_DECLARE(apr_time_t) ap_cache_hex2usec(const char *x);
+CACHE_DECLARE(void) ap_cache_usec2hex(apr_time_t j, char *y);
+CACHE_DECLARE(char *) ap_cache_generate_name(apr_pool_t *p, int dirlevels,
+ int dirlength,
+ const char *name);
+CACHE_DECLARE(cache_provider_list *)ap_cache_get_providers(request_rec *r, cache_server_conf *conf, apr_uri_t uri);
+CACHE_DECLARE(int) ap_cache_liststr(apr_pool_t *p, const char *list,
+ const char *key, char **val);
+CACHE_DECLARE(const char *)ap_cache_tokstr(apr_pool_t *p, const char *list, const char **str);
+
+/* Create a new table consisting of those elements from a request_rec's
+ * headers_out that are allowed to be stored in a cache
+ */
+CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_hdrs_out(apr_pool_t *pool,
+ apr_table_t *t,
+ server_rec *s);
+
+/**
+ * cache_storage.c
+ */
+int cache_remove_url(cache_request_rec *cache, apr_pool_t *p);
+int cache_create_entity(request_rec *r, apr_off_t size);
+int cache_select(request_rec *r);
+apr_status_t cache_generate_key_default( request_rec *r, apr_pool_t*p, char**key );
+/**
+ * create a key for the cache based on the request record
+ * this is the 'default' version, which can be overridden by a default function
+ */
+const char* cache_create_key( request_rec*r );
+
+/*
+apr_status_t cache_store_entity_headers(cache_handle_t *h, request_rec *r, cache_info *info);
+apr_status_t cache_store_entity_body(cache_handle_t *h, request_rec *r, apr_bucket_brigade *bb);
+
+apr_status_t cache_recall_entity_headers(cache_handle_t *h, request_rec *r);
+apr_status_t cache_recall_entity_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb);
+*/
+
+/* hooks */
+
+/* Create a set of CACHE_DECLARE(type), CACHE_DECLARE_NONSTD(type) and
+ * CACHE_DECLARE_DATA with appropriate export and import tags for the platform
+ */
+#if !defined(WIN32)
+#define CACHE_DECLARE(type) type
+#define CACHE_DECLARE_NONSTD(type) type
+#define CACHE_DECLARE_DATA
+#elif defined(CACHE_DECLARE_STATIC)
+#define CACHE_DECLARE(type) type __stdcall
+#define CACHE_DECLARE_NONSTD(type) type
+#define CACHE_DECLARE_DATA
+#elif defined(CACHE_DECLARE_EXPORT)
+#define CACHE_DECLARE(type) __declspec(dllexport) type __stdcall
+#define CACHE_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define CACHE_DECLARE_DATA __declspec(dllexport)
+#else
+#define CACHE_DECLARE(type) __declspec(dllimport) type __stdcall
+#define CACHE_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define CACHE_DECLARE_DATA __declspec(dllimport)
+#endif
+
+APR_DECLARE_OPTIONAL_FN(apr_status_t,
+ ap_cache_generate_key,
+ (request_rec *r, apr_pool_t*p, char**key ));
+
+
+#endif /*MOD_CACHE_H*/
+/** @} */
diff --git a/modules/cache/mod_cache.imp b/modules/cache/mod_cache.imp
new file mode 100644
index 00000000..c63910a4
--- /dev/null
+++ b/modules/cache/mod_cache.imp
@@ -0,0 +1,9 @@
+ (MODCACHE)
+ ap_cache_get_providers,
+ ap_cache_liststr,
+ ap_cache_tokstr,
+ ap_cache_hex2usec,
+ ap_cache_usec2hex,
+ ap_cache_cacheable_hdrs_out,
+ ap_cache_generate_name
+
diff --git a/modules/cache/mod_disk_cache.c b/modules/cache/mod_disk_cache.c
new file mode 100644
index 00000000..15e333c0
--- /dev/null
+++ b/modules/cache/mod_disk_cache.c
@@ -0,0 +1,1174 @@
+/* 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.
+ */
+
+#include "apr_file_io.h"
+#include "apr_strings.h"
+#include "mod_cache.h"
+#include "mod_disk_cache.h"
+#include "ap_provider.h"
+#include "util_filter.h"
+#include "util_script.h"
+#include "util_charset.h"
+
+/*
+ * mod_disk_cache: Disk Based HTTP 1.1 Cache.
+ *
+ * Flow to Find the .data file:
+ * Incoming client requests URI /foo/bar/baz
+ * Generate <hash> off of /foo/bar/baz
+ * Open <hash>.header
+ * Read in <hash>.header file (may contain Format #1 or Format #2)
+ * If format #1 (Contains a list of Vary Headers):
+ * Use each header name (from .header) with our request values (headers_in) to
+ * regenerate <hash> using HeaderName+HeaderValue+.../foo/bar/baz
+ * re-read in <hash>.header (must be format #2)
+ * read in <hash>.data
+ *
+ * Format #1:
+ * apr_uint32_t format;
+ * apr_time_t expire;
+ * apr_array_t vary_headers (delimited by CRLF)
+ *
+ * Format #2:
+ * disk_cache_info_t (first sizeof(apr_uint32_t) bytes is the format)
+ * entity name (dobj->name) [length is in disk_cache_info_t->name_len]
+ * r->headers_out (delimited by CRLF)
+ * CRLF
+ * r->headers_in (delimited by CRLF)
+ * CRLF
+ */
+
+module AP_MODULE_DECLARE_DATA disk_cache_module;
+
+/* Forward declarations */
+static int remove_entity(cache_handle_t *h);
+static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *i);
+static apr_status_t store_body(cache_handle_t *h, request_rec *r, apr_bucket_brigade *b);
+static apr_status_t recall_headers(cache_handle_t *h, request_rec *r);
+static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb);
+static apr_status_t read_array(request_rec *r, apr_array_header_t* arr,
+ apr_file_t *file);
+
+/*
+ * Local static functions
+ */
+
+static char *header_file(apr_pool_t *p, disk_cache_conf *conf,
+ disk_cache_object_t *dobj, const char *name)
+{
+ if (!dobj->hashfile) {
+ dobj->hashfile = ap_cache_generate_name(p, conf->dirlevels,
+ conf->dirlength, name);
+ }
+
+ if (dobj->prefix) {
+ return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX, "/",
+ dobj->hashfile, CACHE_HEADER_SUFFIX, NULL);
+ }
+ else {
+ return apr_pstrcat(p, conf->cache_root, "/", dobj->hashfile,
+ CACHE_HEADER_SUFFIX, NULL);
+ }
+}
+
+static char *data_file(apr_pool_t *p, disk_cache_conf *conf,
+ disk_cache_object_t *dobj, const char *name)
+{
+ if (!dobj->hashfile) {
+ dobj->hashfile = ap_cache_generate_name(p, conf->dirlevels,
+ conf->dirlength, name);
+ }
+
+ if (dobj->prefix) {
+ return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX, "/",
+ dobj->hashfile, CACHE_DATA_SUFFIX, NULL);
+ }
+ else {
+ return apr_pstrcat(p, conf->cache_root, "/", dobj->hashfile,
+ CACHE_DATA_SUFFIX, NULL);
+ }
+}
+
+static void mkdir_structure(disk_cache_conf *conf, const char *file, apr_pool_t *pool)
+{
+ apr_status_t rv;
+ char *p;
+
+ for (p = (char*)file + conf->cache_root_len + 1;;) {
+ p = strchr(p, '/');
+ if (!p)
+ break;
+ *p = '\0';
+
+ rv = apr_dir_make(file,
+ APR_UREAD|APR_UWRITE|APR_UEXECUTE, pool);
+ if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) {
+ /* XXX */
+ }
+ *p = '/';
+ ++p;
+ }
+}
+
+/* htcacheclean may remove directories underneath us.
+ * So, we'll try renaming three times at a cost of 0.002 seconds.
+ */
+static apr_status_t safe_file_rename(disk_cache_conf *conf,
+ const char *src, const char *dest,
+ apr_pool_t *pool)
+{
+ apr_status_t rv;
+
+ rv = apr_file_rename(src, dest, pool);
+
+ if (rv != APR_SUCCESS) {
+ int i;
+
+ for (i = 0; i < 2 && rv != APR_SUCCESS; i++) {
+ /* 1000 micro-seconds aka 0.001 seconds. */
+ apr_sleep(1000);
+
+ mkdir_structure(conf, dest, pool);
+
+ rv = apr_file_rename(src, dest, pool);
+ }
+ }
+
+ return rv;
+}
+
+static apr_status_t file_cache_el_final(disk_cache_object_t *dobj,
+ request_rec *r)
+{
+ /* move the data over */
+ if (dobj->tfd) {
+ apr_status_t rv;
+
+ apr_file_close(dobj->tfd);
+
+ /* This assumes that the tempfile is on the same file system
+ * as the cache_root. If not, then we need a file copy/move
+ * rather than a rename.
+ */
+ rv = apr_file_rename(dobj->tempfile, dobj->datafile, r->pool);
+ if (rv != APR_SUCCESS) {
+ /* XXX log */
+ }
+
+ dobj->tfd = NULL;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t file_cache_errorcleanup(disk_cache_object_t *dobj, request_rec *r)
+{
+ /* Remove the header file and the body file. */
+ apr_file_remove(dobj->hdrsfile, r->pool);
+ apr_file_remove(dobj->datafile, r->pool);
+
+ /* If we opened the temporary data file, close and remove it. */
+ if (dobj->tfd) {
+ apr_file_close(dobj->tfd);
+ apr_file_remove(dobj->tempfile, r->pool);
+ dobj->tfd = NULL;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* These two functions get and put state information into the data
+ * file for an ap_cache_el, this state information will be read
+ * and written transparent to clients of this module
+ */
+static int file_cache_recall_mydata(apr_file_t *fd, cache_info *info,
+ disk_cache_object_t *dobj, request_rec *r)
+{
+ apr_status_t rv;
+ char *urlbuff;
+ disk_cache_info_t disk_info;
+ apr_size_t len;
+
+ /* read the data from the cache file */
+ len = sizeof(disk_cache_info_t);
+ rv = apr_file_read_full(fd, &disk_info, len, &len);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* Store it away so we can get it later. */
+ dobj->disk_info = disk_info;
+
+ info->status = disk_info.status;
+ info->date = disk_info.date;
+ info->expire = disk_info.expire;
+ info->request_time = disk_info.request_time;
+ info->response_time = disk_info.response_time;
+
+ /* Note that we could optimize this by conditionally doing the palloc
+ * depending upon the size. */
+ urlbuff = apr_palloc(r->pool, disk_info.name_len + 1);
+ len = disk_info.name_len;
+ rv = apr_file_read_full(fd, urlbuff, len, &len);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ urlbuff[disk_info.name_len] = '\0';
+
+ /* check that we have the same URL */
+ /* Would strncmp be correct? */
+ if (strcmp(urlbuff, dobj->name) != 0) {
+ return APR_EGENERAL;
+ }
+
+ return APR_SUCCESS;
+}
+
+static const char* regen_key(apr_pool_t *p, apr_table_t *headers,
+ apr_array_header_t *varray, const char *oldkey)
+{
+ struct iovec *iov;
+ int i, k;
+ int nvec;
+ const char *header;
+ const char **elts;
+
+ nvec = (varray->nelts * 2) + 1;
+ iov = apr_palloc(p, sizeof(struct iovec) * nvec);
+ elts = (const char **) varray->elts;
+
+ /* TODO:
+ * - Handle multiple-value headers better. (sort them?)
+ * - Handle Case in-sensitive Values better.
+ * This isn't the end of the world, since it just lowers the cache
+ * hit rate, but it would be nice to fix.
+ *
+ * The majority are case insenstive if they are values (encoding etc).
+ * Most of rfc2616 is case insensitive on header contents.
+ *
+ * So the better solution may be to identify headers which should be
+ * treated case-sensitive?
+ * HTTP URI's (3.2.3) [host and scheme are insensitive]
+ * HTTP method (5.1.1)
+ * HTTP-date values (3.3.1)
+ * 3.7 Media Types [exerpt]
+ * The type, subtype, and parameter attribute names are case-
+ * insensitive. Parameter values might or might not be case-sensitive,
+ * depending on the semantics of the parameter name.
+ * 4.20 Except [exerpt]
+ * Comparison of expectation values is case-insensitive for unquoted
+ * tokens (including the 100-continue token), and is case-sensitive for
+ * quoted-string expectation-extensions.
+ */
+
+ for(i=0, k=0; i < varray->nelts; i++) {
+ header = apr_table_get(headers, elts[i]);
+ if (!header) {
+ header = "";
+ }
+ iov[k].iov_base = (char*) elts[i];
+ iov[k].iov_len = strlen(elts[i]);
+ k++;
+ iov[k].iov_base = (char*) header;
+ iov[k].iov_len = strlen(header);
+ k++;
+ }
+ iov[k].iov_base = (char*) oldkey;
+ iov[k].iov_len = strlen(oldkey);
+ k++;
+
+ return apr_pstrcatv(p, iov, k, NULL);
+}
+
+static int array_alphasort(const void *fn1, const void *fn2)
+{
+ return strcmp(*(char**)fn1, *(char**)fn2);
+}
+
+static void tokens_to_array(apr_pool_t *p, const char *data,
+ apr_array_header_t *arr)
+{
+ char *token;
+
+ while ((token = ap_get_list_item(p, &data)) != NULL) {
+ *((const char **) apr_array_push(arr)) = token;
+ }
+
+ /* Sort it so that "Vary: A, B" and "Vary: B, A" are stored the same. */
+ qsort((void *) arr->elts, arr->nelts,
+ sizeof(char *), array_alphasort);
+}
+
+/*
+ * Hook and mod_cache callback functions
+ */
+static int create_entity(cache_handle_t *h, request_rec *r, const char *key, apr_off_t len)
+{
+ disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
+ &disk_cache_module);
+ cache_object_t *obj;
+ disk_cache_object_t *dobj;
+
+ if (conf->cache_root == NULL) {
+ return DECLINED;
+ }
+
+ /* Allocate and initialize cache_object_t and disk_cache_object_t */
+ h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(*obj));
+ obj->vobj = dobj = apr_pcalloc(r->pool, sizeof(*dobj));
+
+ obj->key = apr_pstrdup(r->pool, key);
+
+ dobj->name = obj->key;
+ dobj->prefix = NULL;
+ /* Save the cache root */
+ dobj->root = apr_pstrndup(r->pool, conf->cache_root, conf->cache_root_len);
+ dobj->root_len = conf->cache_root_len;
+ dobj->datafile = data_file(r->pool, conf, dobj, key);
+ dobj->hdrsfile = header_file(r->pool, conf, dobj, key);
+ dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL);
+
+ return OK;
+}
+
+static int open_entity(cache_handle_t *h, request_rec *r, const char *key)
+{
+ apr_uint32_t format;
+ apr_size_t len;
+ const char *nkey;
+ apr_status_t rc;
+ static int error_logged = 0;
+ disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
+ &disk_cache_module);
+ apr_finfo_t finfo;
+ cache_object_t *obj;
+ cache_info *info;
+ disk_cache_object_t *dobj;
+ int flags;
+
+ h->cache_obj = NULL;
+
+ /* Look up entity keyed to 'url' */
+ if (conf->cache_root == NULL) {
+ if (!error_logged) {
+ error_logged = 1;
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "disk_cache: Cannot cache files to disk without a CacheRoot specified.");
+ }
+ return DECLINED;
+ }
+
+ /* Create and init the cache object */
+ h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(cache_object_t));
+ obj->vobj = dobj = apr_pcalloc(r->pool, sizeof(disk_cache_object_t));
+
+ info = &(obj->info);
+
+ /* Open the headers file */
+ dobj->prefix = NULL;
+
+ /* Save the cache root */
+ dobj->root = apr_pstrndup(r->pool, conf->cache_root, conf->cache_root_len);
+ dobj->root_len = conf->cache_root_len;
+
+ dobj->hdrsfile = header_file(r->pool, conf, dobj, key);
+ flags = APR_READ|APR_BINARY|APR_BUFFERED;
+ rc = apr_file_open(&dobj->hfd, dobj->hdrsfile, flags, 0, r->pool);
+ if (rc != APR_SUCCESS) {
+ return DECLINED;
+ }
+
+ /* read the format from the cache file */
+ len = sizeof(format);
+ apr_file_read_full(dobj->hfd, &format, len, &len);
+
+ if (format == VARY_FORMAT_VERSION) {
+ apr_array_header_t* varray;
+ apr_time_t expire;
+
+ len = sizeof(expire);
+ apr_file_read_full(dobj->hfd, &expire, len, &len);
+
+ if (expire < r->request_time) {
+ return DECLINED;
+ }
+
+ varray = apr_array_make(r->pool, 5, sizeof(char*));
+ rc = read_array(r, varray, dobj->hfd);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc, r->server,
+ "disk_cache: Cannot parse vary header file: %s",
+ dobj->hdrsfile);
+ return DECLINED;
+ }
+ apr_file_close(dobj->hfd);
+
+ nkey = regen_key(r->pool, r->headers_in, varray, key);
+
+ dobj->hashfile = NULL;
+ dobj->prefix = dobj->hdrsfile;
+ dobj->hdrsfile = header_file(r->pool, conf, dobj, nkey);
+
+ flags = APR_READ|APR_BINARY|APR_BUFFERED;
+ rc = apr_file_open(&dobj->hfd, dobj->hdrsfile, flags, 0, r->pool);
+ if (rc != APR_SUCCESS) {
+ return DECLINED;
+ }
+ }
+ else if (format != DISK_FORMAT_VERSION) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "cache_disk: File '%s' has a version mismatch. File had version: %d.",
+ dobj->hdrsfile, format);
+ return DECLINED;
+ }
+ else {
+ apr_off_t offset = 0;
+ /* This wasn't a Vary Format file, so we must seek to the
+ * start of the file again, so that later reads work.
+ */
+ apr_file_seek(dobj->hfd, APR_SET, &offset);
+ nkey = key;
+ }
+
+ obj->key = nkey;
+ dobj->key = nkey;
+ dobj->name = key;
+ dobj->datafile = data_file(r->pool, conf, dobj, nkey);
+ dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL);
+
+ /* Open the data file */
+ flags = APR_READ|APR_BINARY;
+#ifdef APR_SENDFILE_ENABLED
+ flags |= APR_SENDFILE_ENABLED;
+#endif
+ rc = apr_file_open(&dobj->fd, dobj->datafile, flags, 0, r->pool);
+ if (rc != APR_SUCCESS) {
+ /* XXX: Log message */
+ return DECLINED;
+ }
+
+ rc = apr_file_info_get(&finfo, APR_FINFO_SIZE, dobj->fd);
+ if (rc == APR_SUCCESS) {
+ dobj->file_size = finfo.size;
+ }
+
+ /* Read the bytes to setup the cache_info fields */
+ rc = file_cache_recall_mydata(dobj->hfd, info, dobj, r);
+ if (rc != APR_SUCCESS) {
+ /* XXX log message */
+ return DECLINED;
+ }
+
+ /* Initialize the cache_handle callback functions */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "disk_cache: Recalled cached URL info header %s", dobj->name);
+ return OK;
+}
+
+static int remove_entity(cache_handle_t *h)
+{
+ /* Null out the cache object pointer so next time we start from scratch */
+ h->cache_obj = NULL;
+ return OK;
+}
+
+static int remove_url(cache_handle_t *h, apr_pool_t *p)
+{
+ apr_status_t rc;
+ disk_cache_object_t *dobj;
+
+ /* Get disk cache object from cache handle */
+ dobj = (disk_cache_object_t *) h->cache_obj->vobj;
+ if (!dobj) {
+ return DECLINED;
+ }
+
+ /* Delete headers file */
+ if (dobj->hdrsfile) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ "disk_cache: Deleting %s from cache.", dobj->hdrsfile);
+
+ rc = apr_file_remove(dobj->hdrsfile, p);
+ if ((rc != APR_SUCCESS) && !APR_STATUS_IS_ENOENT(rc)) {
+ /* Will only result in an output if httpd is started with -e debug.
+ * For reason see log_error_core for the case s == NULL.
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rc, NULL,
+ "disk_cache: Failed to delete headers file %s from cache.",
+ dobj->hdrsfile);
+ return DECLINED;
+ }
+ }
+
+ /* Delete data file */
+ if (dobj->datafile) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ "disk_cache: Deleting %s from cache.", dobj->datafile);
+
+ rc = apr_file_remove(dobj->datafile, p);
+ if ((rc != APR_SUCCESS) && !APR_STATUS_IS_ENOENT(rc)) {
+ /* Will only result in an output if httpd is started with -e debug.
+ * For reason see log_error_core for the case s == NULL.
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rc, NULL,
+ "disk_cache: Failed to delete data file %s from cache.",
+ dobj->datafile);
+ return DECLINED;
+ }
+ }
+
+ /* now delete directories as far as possible up to our cache root */
+ if (dobj->root) {
+ const char *str_to_copy;
+
+ str_to_copy = dobj->hdrsfile ? dobj->hdrsfile : dobj->datafile;
+ if (str_to_copy) {
+ char *dir, *slash, *q;
+
+ dir = apr_pstrdup(p, str_to_copy);
+
+ /* remove filename */
+ slash = strrchr(dir, '/');
+ *slash = '\0';
+
+ /*
+ * now walk our way back to the cache root, delete everything
+ * in the way as far as possible
+ *
+ * Note: due to the way we constructed the file names in
+ * header_file and data_file, we are guaranteed that the
+ * cache_root is suffixed by at least one '/' which will be
+ * turned into a terminating null by this loop. Therefore,
+ * we won't either delete or go above our cache root.
+ */
+ for (q = dir + dobj->root_len; *q ; ) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ "disk_cache: Deleting directory %s from cache",
+ dir);
+
+ rc = apr_dir_remove(dir, p);
+ if (rc != APR_SUCCESS && !APR_STATUS_IS_ENOENT(rc)) {
+ break;
+ }
+ slash = strrchr(q, '/');
+ *slash = '\0';
+ }
+ }
+ }
+
+ return OK;
+}
+
+static apr_status_t read_array(request_rec *r, apr_array_header_t* arr,
+ apr_file_t *file)
+{
+ char w[MAX_STRING_LEN];
+ int p;
+ apr_status_t rv;
+
+ while (1) {
+ rv = apr_file_gets(w, MAX_STRING_LEN - 1, file);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Premature end of vary array.");
+ return rv;
+ }
+
+ p = strlen(w);
+ if (p > 0 && w[p - 1] == '\n') {
+ if (p > 1 && w[p - 2] == CR) {
+ w[p - 2] = '\0';
+ }
+ else {
+ w[p - 1] = '\0';
+ }
+ }
+
+ /* If we've finished reading the array, break out of the loop. */
+ if (w[0] == '\0') {
+ break;
+ }
+
+ *((const char **) apr_array_push(arr)) = apr_pstrdup(r->pool, w);
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t store_array(apr_file_t *fd, apr_array_header_t* arr)
+{
+ int i;
+ apr_status_t rv;
+ struct iovec iov[2];
+ apr_size_t amt;
+ const char **elts;
+
+ elts = (const char **) arr->elts;
+
+ for (i = 0; i < arr->nelts; i++) {
+ iov[0].iov_base = (char*) elts[i];
+ iov[0].iov_len = strlen(elts[i]);
+ iov[1].iov_base = CRLF;
+ iov[1].iov_len = sizeof(CRLF) - 1;
+
+ rv = apr_file_writev(fd, (const struct iovec *) &iov, 2,
+ &amt);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ iov[0].iov_base = CRLF;
+ iov[0].iov_len = sizeof(CRLF) - 1;
+
+ return apr_file_writev(fd, (const struct iovec *) &iov, 1,
+ &amt);
+}
+
+static apr_status_t read_table(cache_handle_t *handle, request_rec *r,
+ apr_table_t *table, apr_file_t *file)
+{
+ char w[MAX_STRING_LEN];
+ char *l;
+ int p;
+ apr_status_t rv;
+
+ while (1) {
+
+ /* ### What about APR_EOF? */
+ rv = apr_file_gets(w, MAX_STRING_LEN - 1, file);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Premature end of cache headers.");
+ return rv;
+ }
+
+ /* Delete terminal (CR?)LF */
+
+ p = strlen(w);
+ /* Indeed, the host's '\n':
+ '\012' for UNIX; '\015' for MacOS; '\025' for OS/390
+ -- whatever the script generates.
+ */
+ if (p > 0 && w[p - 1] == '\n') {
+ if (p > 1 && w[p - 2] == CR) {
+ w[p - 2] = '\0';
+ }
+ else {
+ w[p - 1] = '\0';
+ }
+ }
+
+ /* If we've finished reading the headers, break out of the loop. */
+ if (w[0] == '\0') {
+ break;
+ }
+
+#if APR_CHARSET_EBCDIC
+ /* Chances are that we received an ASCII header text instead of
+ * the expected EBCDIC header lines. Try to auto-detect:
+ */
+ if (!(l = strchr(w, ':'))) {
+ int maybeASCII = 0, maybeEBCDIC = 0;
+ unsigned char *cp, native;
+ apr_size_t inbytes_left, outbytes_left;
+
+ for (cp = w; *cp != '\0'; ++cp) {
+ native = apr_xlate_conv_byte(ap_hdrs_from_ascii, *cp);
+ if (apr_isprint(*cp) && !apr_isprint(native))
+ ++maybeEBCDIC;
+ if (!apr_isprint(*cp) && apr_isprint(native))
+ ++maybeASCII;
+ }
+ if (maybeASCII > maybeEBCDIC) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "CGI Interface Error: Script headers apparently ASCII: (CGI = %s)",
+ r->filename);
+ inbytes_left = outbytes_left = cp - w;
+ apr_xlate_conv_buffer(ap_hdrs_from_ascii,
+ w, &inbytes_left, w, &outbytes_left);
+ }
+ }
+#endif /*APR_CHARSET_EBCDIC*/
+
+ /* if we see a bogus header don't ignore it. Shout and scream */
+ if (!(l = strchr(w, ':'))) {
+ return APR_EGENERAL;
+ }
+
+ *l++ = '\0';
+ while (*l && apr_isspace(*l)) {
+ ++l;
+ }
+
+ apr_table_add(table, w, l);
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Reads headers from a buffer and returns an array of headers.
+ * Returns NULL on file error
+ * This routine tries to deal with too long lines and continuation lines.
+ * @@@: XXX: FIXME: currently the headers are passed thru un-merged.
+ * Is that okay, or should they be collapsed where possible?
+ */
+static apr_status_t recall_headers(cache_handle_t *h, request_rec *r)
+{
+ disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj;
+
+ /* This case should not happen... */
+ if (!dobj->hfd) {
+ /* XXX log message */
+ return APR_NOTFOUND;
+ }
+
+ h->req_hdrs = apr_table_make(r->pool, 20);
+ h->resp_hdrs = apr_table_make(r->pool, 20);
+
+ /* Call routine to read the header lines/status line */
+ read_table(h, r, h->resp_hdrs, dobj->hfd);
+ read_table(h, r, h->req_hdrs, dobj->hfd);
+
+ apr_file_close(dobj->hfd);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "disk_cache: Recalled headers for URL %s", dobj->name);
+ return APR_SUCCESS;
+}
+
+static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb)
+{
+ apr_bucket *e;
+ disk_cache_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj;
+
+ e = apr_bucket_file_create(dobj->fd, 0, (apr_size_t) dobj->file_size, p,
+ bb->bucket_alloc);
+ APR_BRIGADE_INSERT_HEAD(bb, e);
+ e = apr_bucket_eos_create(bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t store_table(apr_file_t *fd, apr_table_t *table)
+{
+ int i;
+ apr_status_t rv;
+ struct iovec iov[4];
+ apr_size_t amt;
+ apr_table_entry_t *elts;
+
+ elts = (apr_table_entry_t *) apr_table_elts(table)->elts;
+ for (i = 0; i < apr_table_elts(table)->nelts; ++i) {
+ if (elts[i].key != NULL) {
+ iov[0].iov_base = elts[i].key;
+ iov[0].iov_len = strlen(elts[i].key);
+ iov[1].iov_base = ": ";
+ iov[1].iov_len = sizeof(": ") - 1;
+ iov[2].iov_base = elts[i].val;
+ iov[2].iov_len = strlen(elts[i].val);
+ iov[3].iov_base = CRLF;
+ iov[3].iov_len = sizeof(CRLF) - 1;
+
+ rv = apr_file_writev(fd, (const struct iovec *) &iov, 4,
+ &amt);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ }
+ iov[0].iov_base = CRLF;
+ iov[0].iov_len = sizeof(CRLF) - 1;
+ rv = apr_file_writev(fd, (const struct iovec *) &iov, 1,
+ &amt);
+ return rv;
+}
+
+static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *info)
+{
+ disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
+ &disk_cache_module);
+ apr_status_t rv;
+ apr_size_t amt;
+ disk_cache_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj;
+
+ disk_cache_info_t disk_info;
+ struct iovec iov[2];
+
+ /* This is flaky... we need to manage the cache_info differently */
+ h->cache_obj->info = *info;
+
+ if (r->headers_out) {
+ const char *tmp;
+
+ tmp = apr_table_get(r->headers_out, "Vary");
+
+ if (tmp) {
+ apr_array_header_t* varray;
+ apr_uint32_t format = VARY_FORMAT_VERSION;
+
+ mkdir_structure(conf, dobj->hdrsfile, r->pool);
+
+ rv = apr_file_mktemp(&dobj->tfd, dobj->tempfile,
+ APR_CREATE | APR_WRITE | APR_BINARY | APR_EXCL,
+ r->pool);
+
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ amt = sizeof(format);
+ apr_file_write(dobj->tfd, &format, &amt);
+
+ amt = sizeof(info->expire);
+ apr_file_write(dobj->tfd, &info->expire, &amt);
+
+ varray = apr_array_make(r->pool, 6, sizeof(char*));
+ tokens_to_array(r->pool, tmp, varray);
+
+ store_array(dobj->tfd, varray);
+
+ apr_file_close(dobj->tfd);
+
+ dobj->tfd = NULL;
+
+ rv = safe_file_rename(conf, dobj->tempfile, dobj->hdrsfile,
+ r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
+ "disk_cache: rename tempfile to varyfile failed: %s -> %s",
+ dobj->tempfile, dobj->hdrsfile);
+ return rv;
+ }
+
+ dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL);
+ tmp = regen_key(r->pool, r->headers_in, varray, dobj->name);
+ dobj->prefix = dobj->hdrsfile;
+ dobj->hashfile = NULL;
+ dobj->datafile = data_file(r->pool, conf, dobj, tmp);
+ dobj->hdrsfile = header_file(r->pool, conf, dobj, tmp);
+ }
+ }
+
+
+ rv = apr_file_mktemp(&dobj->hfd, dobj->tempfile,
+ APR_CREATE | APR_WRITE | APR_BINARY |
+ APR_BUFFERED | APR_EXCL, r->pool);
+
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ dobj->name = h->cache_obj->key;
+
+ disk_info.format = DISK_FORMAT_VERSION;
+ disk_info.date = info->date;
+ disk_info.expire = info->expire;
+ disk_info.entity_version = dobj->disk_info.entity_version++;
+ disk_info.request_time = info->request_time;
+ disk_info.response_time = info->response_time;
+ disk_info.status = info->status;
+
+ disk_info.name_len = strlen(dobj->name);
+
+ iov[0].iov_base = (void*)&disk_info;
+ iov[0].iov_len = sizeof(disk_cache_info_t);
+ iov[1].iov_base = (void*)dobj->name;
+ iov[1].iov_len = disk_info.name_len;
+
+ rv = apr_file_writev(dobj->hfd, (const struct iovec *) &iov, 2, &amt);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ if (r->headers_out) {
+ apr_table_t *headers_out;
+
+ headers_out = ap_cache_cacheable_hdrs_out(r->pool, r->headers_out,
+ r->server);
+
+ if (!apr_table_get(headers_out, "Content-Type")
+ && r->content_type) {
+ apr_table_setn(headers_out, "Content-Type",
+ ap_make_content_type(r, r->content_type));
+ }
+
+ headers_out = apr_table_overlay(r->pool, headers_out,
+ r->err_headers_out);
+ rv = store_table(dobj->hfd, headers_out);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ /* Parse the vary header and dump those fields from the headers_in. */
+ /* FIXME: Make call to the same thing cache_select calls to crack Vary. */
+ if (r->headers_in) {
+ apr_table_t *headers_in;
+
+ headers_in = ap_cache_cacheable_hdrs_out(r->pool, r->headers_in,
+ r->server);
+ rv = store_table(dobj->hfd, headers_in);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ apr_file_close(dobj->hfd); /* flush and close */
+
+ /* Remove old file with the same name. If remove fails, then
+ * perhaps we need to create the directory tree where we are
+ * about to write the new headers file.
+ */
+ rv = apr_file_remove(dobj->hdrsfile, r->pool);
+ if (rv != APR_SUCCESS) {
+ mkdir_structure(conf, dobj->hdrsfile, r->pool);
+ }
+
+ rv = safe_file_rename(conf, dobj->tempfile, dobj->hdrsfile, r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
+ "disk_cache: rename tempfile to hdrsfile failed: %s -> %s",
+ dobj->tempfile, dobj->hdrsfile);
+ return rv;
+ }
+
+ dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "disk_cache: Stored headers for URL %s", dobj->name);
+ return APR_SUCCESS;
+}
+
+static apr_status_t store_body(cache_handle_t *h, request_rec *r,
+ apr_bucket_brigade *bb)
+{
+ apr_bucket *e;
+ apr_status_t rv;
+ disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj;
+ disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
+ &disk_cache_module);
+
+ /* We write to a temp file and then atomically rename the file over
+ * in file_cache_el_final().
+ */
+ if (!dobj->tfd) {
+ rv = apr_file_mktemp(&dobj->tfd, dobj->tempfile,
+ APR_CREATE | APR_WRITE | APR_BINARY |
+ APR_BUFFERED | APR_EXCL, r->pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ dobj->file_size = 0;
+ }
+
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb);
+ e = APR_BUCKET_NEXT(e))
+ {
+ const char *str;
+ apr_size_t length, written;
+ rv = apr_bucket_read(e, &str, &length, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "cache_disk: Error when reading bucket for URL %s",
+ h->cache_obj->key);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ file_cache_errorcleanup(dobj, r);
+ return rv;
+ }
+ rv = apr_file_write_full(dobj->tfd, str, length, &written);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "cache_disk: Error when writing cache file for URL %s",
+ h->cache_obj->key);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ file_cache_errorcleanup(dobj, r);
+ return rv;
+ }
+ dobj->file_size += written;
+ if (dobj->file_size > conf->maxfs) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "cache_disk: URL %s failed the size check "
+ "(%" APR_OFF_T_FMT ">%" APR_SIZE_T_FMT ")",
+ h->cache_obj->key, dobj->file_size, conf->maxfs);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ file_cache_errorcleanup(dobj, r);
+ return APR_EGENERAL;
+ }
+ }
+
+ /* Was this the final bucket? If yes, close the temp file and perform
+ * sanity checks.
+ */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
+ if (r->connection->aborted || r->no_cache) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
+ "disk_cache: Discarding body for URL %s "
+ "because connection has been aborted.",
+ h->cache_obj->key);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ file_cache_errorcleanup(dobj, r);
+ return APR_EGENERAL;
+ }
+ if (dobj->file_size < conf->minfs) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "cache_disk: URL %s failed the size check "
+ "(%" APR_OFF_T_FMT "<%" APR_SIZE_T_FMT ")",
+ h->cache_obj->key, dobj->file_size, conf->minfs);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ file_cache_errorcleanup(dobj, r);
+ return APR_EGENERAL;
+ }
+
+ /* All checks were fine. Move tempfile to final destination */
+ /* Link to the perm file, and close the descriptor */
+ file_cache_el_final(dobj, r);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "disk_cache: Body for URL %s cached.", dobj->name);
+ }
+
+ return APR_SUCCESS;
+}
+
+static void *create_config(apr_pool_t *p, server_rec *s)
+{
+ disk_cache_conf *conf = apr_pcalloc(p, sizeof(disk_cache_conf));
+
+ /* XXX: Set default values */
+ conf->dirlevels = DEFAULT_DIRLEVELS;
+ conf->dirlength = DEFAULT_DIRLENGTH;
+ conf->maxfs = DEFAULT_MAX_FILE_SIZE;
+ conf->minfs = DEFAULT_MIN_FILE_SIZE;
+
+ conf->cache_root = NULL;
+ conf->cache_root_len = 0;
+
+ return conf;
+}
+
+/*
+ * mod_disk_cache configuration directives handlers.
+ */
+static const char
+*set_cache_root(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
+ &disk_cache_module);
+ conf->cache_root = arg;
+ conf->cache_root_len = strlen(arg);
+ /* TODO: canonicalize cache_root and strip off any trailing slashes */
+
+ return NULL;
+}
+
+/*
+ * Consider eliminating the next two directives in favor of
+ * Ian's prime number hash...
+ * key = hash_fn( r->uri)
+ * filename = "/key % prime1 /key %prime2/key %prime3"
+ */
+static const char
+*set_cache_dirlevels(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
+ &disk_cache_module);
+ int val = atoi(arg);
+ if (val < 1)
+ return "CacheDirLevels value must be an integer greater than 0";
+ if (val * conf->dirlength > CACHEFILE_LEN)
+ return "CacheDirLevels*CacheDirLength value must not be higher than 20";
+ conf->dirlevels = val;
+ return NULL;
+}
+static const char
+*set_cache_dirlength(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
+ &disk_cache_module);
+ int val = atoi(arg);
+ if (val < 1)
+ return "CacheDirLength value must be an integer greater than 0";
+ if (val * conf->dirlevels > CACHEFILE_LEN)
+ return "CacheDirLevels*CacheDirLength value must not be higher than 20";
+
+ conf->dirlength = val;
+ return NULL;
+}
+
+static const char
+*set_cache_minfs(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
+ &disk_cache_module);
+ conf->minfs = atoi(arg);
+ return NULL;
+}
+static const char
+*set_cache_maxfs(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
+ &disk_cache_module);
+ conf->maxfs = atoi(arg);
+ return NULL;
+}
+
+static const command_rec disk_cache_cmds[] =
+{
+ AP_INIT_TAKE1("CacheRoot", set_cache_root, NULL, RSRC_CONF,
+ "The directory to store cache files"),
+ AP_INIT_TAKE1("CacheDirLevels", set_cache_dirlevels, NULL, RSRC_CONF,
+ "The number of levels of subdirectories in the cache"),
+ AP_INIT_TAKE1("CacheDirLength", set_cache_dirlength, NULL, RSRC_CONF,
+ "The number of characters in subdirectory names"),
+ AP_INIT_TAKE1("CacheMinFileSize", set_cache_minfs, NULL, RSRC_CONF,
+ "The minimum file size to cache a document"),
+ AP_INIT_TAKE1("CacheMaxFileSize", set_cache_maxfs, NULL, RSRC_CONF,
+ "The maximum file size to cache a document"),
+ {NULL}
+};
+
+static const cache_provider cache_disk_provider =
+{
+ &remove_entity,
+ &store_headers,
+ &store_body,
+ &recall_headers,
+ &recall_body,
+ &create_entity,
+ &open_entity,
+ &remove_url,
+};
+
+static void disk_cache_register_hook(apr_pool_t *p)
+{
+ /* cache initializer */
+ ap_register_provider(p, CACHE_PROVIDER_GROUP, "disk", "0",
+ &cache_disk_provider);
+}
+
+module AP_MODULE_DECLARE_DATA disk_cache_module = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ create_config, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ disk_cache_cmds, /* command apr_table_t */
+ disk_cache_register_hook /* register hooks */
+};
diff --git a/modules/cache/mod_disk_cache.dsp b/modules/cache/mod_disk_cache.dsp
new file mode 100644
index 00000000..92a47194
--- /dev/null
+++ b/modules/cache/mod_disk_cache.dsp
@@ -0,0 +1,103 @@
+# Microsoft Developer Studio Project File - Name="mod_disk_cache" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_disk_cache - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_disk_cache.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_disk_cache.mak" CFG="mod_disk_cache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_disk_cache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_disk_cache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_disk_cache - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fd"Release\mod_disk_cache_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_disk_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_disk_cache.so" /d "LONG_NAME=disk_cache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Release/mod_disk_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_disk_cache.so /opt:ref
+
+!ELSEIF "$(CFG)" == "mod_disk_cache - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /Fd"Debug\mod_disk_cache_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_disk_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_disk_cache.so" /d "LONG_NAME=disk_cache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_disk_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_disk_cache.so
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_disk_cache - Win32 Release"
+# Name "mod_disk_cache - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_cache.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_disk_cache.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cache/mod_disk_cache.h b/modules/cache/mod_disk_cache.h
new file mode 100644
index 00000000..d9911795
--- /dev/null
+++ b/modules/cache/mod_disk_cache.h
@@ -0,0 +1,95 @@
+/* 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.
+ */
+
+#ifndef MOD_DISK_CACHE_H
+#define MOD_DISK_CACHE_H
+
+/*
+ * include for mod_disk_cache: Disk Based HTTP 1.1 Cache.
+ */
+
+#define VARY_FORMAT_VERSION 3
+#define DISK_FORMAT_VERSION 4
+
+#define CACHE_HEADER_SUFFIX ".header"
+#define CACHE_DATA_SUFFIX ".data"
+#define CACHE_VDIR_SUFFIX ".vary"
+
+#define AP_TEMPFILE_PREFIX "/"
+#define AP_TEMPFILE_BASE "aptmp"
+#define AP_TEMPFILE_SUFFIX "XXXXXX"
+#define AP_TEMPFILE_BASELEN strlen(AP_TEMPFILE_BASE)
+#define AP_TEMPFILE_NAMELEN strlen(AP_TEMPFILE_BASE AP_TEMPFILE_SUFFIX)
+#define AP_TEMPFILE AP_TEMPFILE_PREFIX AP_TEMPFILE_BASE AP_TEMPFILE_SUFFIX
+
+typedef struct {
+ /* Indicates the format of the header struct stored on-disk. */
+ apr_uint32_t format;
+ /* The HTTP status code returned for this response. */
+ int status;
+ /* The size of the entity name that follows. */
+ apr_size_t name_len;
+ /* The number of times we've cached this entity. */
+ apr_size_t entity_version;
+ /* Miscellaneous time values. */
+ apr_time_t date;
+ apr_time_t expire;
+ apr_time_t request_time;
+ apr_time_t response_time;
+} disk_cache_info_t;
+
+/*
+ * disk_cache_object_t
+ * Pointed to by cache_object_t::vobj
+ */
+typedef struct disk_cache_object {
+ const char *root; /* the location of the cache directory */
+ apr_size_t root_len;
+ char *tempfile; /* temp file tohold the content */
+ const char *prefix;
+ const char *datafile; /* name of file where the data will go */
+ const char *hdrsfile; /* name of file where the hdrs will go */
+ const char *hashfile; /* Computed hash key for this URI */
+ const char *name; /* Requested URI without vary bits - suitable for mortals. */
+ const char *key; /* On-disk prefix; URI with Vary bits (if present) */
+ apr_file_t *fd; /* data file */
+ apr_file_t *hfd; /* headers file */
+ apr_file_t *tfd; /* temporary file for data */
+ apr_off_t file_size; /* File size of the cached data file */
+ disk_cache_info_t disk_info; /* Header information. */
+} disk_cache_object_t;
+
+
+/*
+ * mod_disk_cache configuration
+ */
+/* TODO: Make defaults OS specific */
+#define CACHEFILE_LEN 20 /* must be less than HASH_LEN/2 */
+#define DEFAULT_DIRLEVELS 3
+#define DEFAULT_DIRLENGTH 2
+#define DEFAULT_MIN_FILE_SIZE 1
+#define DEFAULT_MAX_FILE_SIZE 1000000
+
+typedef struct {
+ const char* cache_root;
+ apr_size_t cache_root_len;
+ int dirlevels; /* Number of levels of subdirectories */
+ int dirlength; /* Length of subdirectory names */
+ apr_size_t minfs; /* minumum file size for cached files */
+ apr_size_t maxfs; /* maximum file size for cached files */
+} disk_cache_conf;
+
+#endif /*MOD_DISK_CACHE_H*/
diff --git a/modules/cache/mod_file_cache.c b/modules/cache/mod_file_cache.c
new file mode 100644
index 00000000..17ec6f4a
--- /dev/null
+++ b/modules/cache/mod_file_cache.c
@@ -0,0 +1,417 @@
+/* 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.
+ */
+
+/*
+ * Author: mod_file_cache by Bill Stoddard <stoddard apache.org>
+ * Based on mod_mmap_static by Dean Gaudet <dgaudet arctic.org>
+ *
+ * v0.01: initial implementation
+ */
+
+/*
+ Documentation:
+
+ Some sites have a set of static files that are really busy, and
+ change infrequently (or even on a regular schedule). Save time
+ by caching open handles to these files. This module, unlike
+ mod_mmap_static, caches open file handles, not file content.
+ On systems (like Windows) with heavy system call overhead and
+ that have an efficient sendfile implementation, caching file handles
+ offers several advantages over caching content. First, the file system
+ can manage the memory, allowing infrequently hit cached files to
+ be paged out. Second, since caching open handles does not consume
+ significant resources, it will be possible to enable an AutoLoadCache
+ feature where static files are dynamically loaded in the cache
+ as the server runs. On systems that have file change notification,
+ this module can be enhanced to automatically garbage collect
+ cached files that change on disk.
+
+ This module should work on Unix systems that have sendfile. Place
+ cachefile directives into your configuration to direct files to
+ be cached.
+
+ cachefile /path/to/file1
+ cachefile /path/to/file2
+ ...
+
+ These files are only cached when the server is restarted, so if you
+ change the list, or if the files are changed, then you'll need to
+ restart the server.
+
+ To reiterate that point: if the files are modified *in place*
+ without restarting the server you may end up serving requests that
+ are completely bogus. You should update files by unlinking the old
+ copy and putting a new copy in place.
+
+ There's no such thing as inheriting these files across vhosts or
+ whatever... place the directives in the main server only.
+
+ Known problems:
+
+ Don't use Alias or RewriteRule to move these files around... unless
+ you feel like paying for an extra stat() on each request. This is
+ a deficiency in the Apache API that will hopefully be solved some day.
+ The file will be served out of the file handle cache, but there will be
+ an extra stat() that's a waste.
+*/
+
+#include "apr.h"
+
+#if !(APR_HAS_SENDFILE || APR_HAS_MMAP)
+#error mod_file_cache only works on systems with APR_HAS_SENDFILE or APR_HAS_MMAP
+#endif
+
+#include "apr_mmap.h"
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "apr_buckets.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#define CORE_PRIVATE
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_core.h"
+
+module AP_MODULE_DECLARE_DATA file_cache_module;
+
+typedef struct {
+#if APR_HAS_SENDFILE
+ apr_file_t *file;
+#endif
+ const char *filename;
+ apr_finfo_t finfo;
+ int is_mmapped;
+#if APR_HAS_MMAP
+ apr_mmap_t *mm;
+#endif
+ char mtimestr[APR_RFC822_DATE_LEN];
+ char sizestr[21]; /* big enough to hold any 64-bit file size + null */
+} a_file;
+
+typedef struct {
+ apr_hash_t *fileht;
+} a_server_config;
+
+
+static void *create_server_config(apr_pool_t *p, server_rec *s)
+{
+ a_server_config *sconf = apr_palloc(p, sizeof(*sconf));
+
+ sconf->fileht = apr_hash_make(p);
+ return sconf;
+}
+
+static void cache_the_file(cmd_parms *cmd, const char *filename, int mmap)
+{
+ a_server_config *sconf;
+ a_file *new_file;
+ a_file tmp;
+ apr_file_t *fd = NULL;
+ apr_status_t rc;
+ const char *fspec;
+
+ fspec = ap_server_root_relative(cmd->pool, filename);
+ if (!fspec) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server,
+ "mod_file_cache: invalid file path "
+ "%s, skipping", filename);
+ return;
+ }
+ if ((rc = apr_stat(&tmp.finfo, fspec, APR_FINFO_MIN,
+ cmd->temp_pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
+ "mod_file_cache: unable to stat(%s), skipping", fspec);
+ return;
+ }
+ if (tmp.finfo.filetype != APR_REG) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
+ "mod_file_cache: %s isn't a regular file, skipping", fspec);
+ return;
+ }
+ if (tmp.finfo.size > AP_MAX_SENDFILE) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
+ "mod_file_cache: %s is too large to cache, skipping", fspec);
+ return;
+ }
+
+ rc = apr_file_open(&fd, fspec, APR_READ | APR_BINARY | APR_XTHREAD,
+ APR_OS_DEFAULT, cmd->pool);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
+ "mod_file_cache: unable to open(%s, O_RDONLY), skipping", fspec);
+ return;
+ }
+ apr_file_inherit_set(fd);
+
+ /* WooHoo, we have a file to put in the cache */
+ new_file = apr_pcalloc(cmd->pool, sizeof(a_file));
+ new_file->finfo = tmp.finfo;
+
+#if APR_HAS_MMAP
+ if (mmap) {
+ /* MMAPFile directive. MMAP'ing the file
+ * XXX: APR_HAS_LARGE_FILES issue; need to reject this request if
+ * size is greater than MAX(apr_size_t) (perhaps greater than 1M?).
+ */
+ if ((rc = apr_mmap_create(&new_file->mm, fd, 0,
+ (apr_size_t)new_file->finfo.size,
+ APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) {
+ apr_file_close(fd);
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
+ "mod_file_cache: unable to mmap %s, skipping", filename);
+ return;
+ }
+ apr_file_close(fd);
+ new_file->is_mmapped = TRUE;
+ }
+#endif
+#if APR_HAS_SENDFILE
+ if (!mmap) {
+ /* CacheFile directive. Caching the file handle */
+ new_file->is_mmapped = FALSE;
+ new_file->file = fd;
+ }
+#endif
+
+ new_file->filename = fspec;
+ apr_rfc822_date(new_file->mtimestr, new_file->finfo.mtime);
+ apr_snprintf(new_file->sizestr, sizeof new_file->sizestr, "%" APR_OFF_T_FMT, new_file->finfo.size);
+
+ sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
+ apr_hash_set(sconf->fileht, new_file->filename, strlen(new_file->filename), new_file);
+
+}
+
+static const char *cachefilehandle(cmd_parms *cmd, void *dummy, const char *filename)
+{
+#if APR_HAS_SENDFILE
+ cache_the_file(cmd, filename, 0);
+#else
+ /* Sendfile not supported by this OS */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
+ "mod_file_cache: unable to cache file: %s. Sendfile is not supported on this OS", filename);
+#endif
+ return NULL;
+}
+static const char *cachefilemmap(cmd_parms *cmd, void *dummy, const char *filename)
+{
+#if APR_HAS_MMAP
+ cache_the_file(cmd, filename, 1);
+#else
+ /* MMAP not supported by this OS */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
+ "mod_file_cache: unable to cache file: %s. MMAP is not supported by this OS", filename);
+#endif
+ return NULL;
+}
+
+static int file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ /* Hummm, anything to do here? */
+ return OK;
+}
+
+/* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
+ * bit of a kludge, because we really want to run after core_translate runs.
+ */
+static int file_cache_xlat(request_rec *r)
+{
+ a_server_config *sconf;
+ a_file *match;
+ int res;
+
+ sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
+
+ /* we only operate when at least one cachefile directive was used */
+ if (!apr_hash_count(sconf->fileht)) {
+ return DECLINED;
+ }
+
+ res = ap_core_translate(r);
+ if (res != OK || !r->filename) {
+ return res;
+ }
+
+ /* search the cache */
+ match = (a_file *) apr_hash_get(sconf->fileht, r->filename, APR_HASH_KEY_STRING);
+ if (match == NULL)
+ return DECLINED;
+
+ /* pass search results to handler */
+ ap_set_module_config(r->request_config, &file_cache_module, match);
+
+ /* shortcircuit the get_path_info() stat() calls and stuff */
+ r->finfo = match->finfo;
+ return OK;
+}
+
+static int mmap_handler(request_rec *r, a_file *file)
+{
+#if APR_HAS_MMAP
+ conn_rec *c = r->connection;
+ apr_bucket *b;
+ apr_mmap_t *mm;
+ apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ apr_mmap_dup(&mm, file->mm, r->pool);
+ b = apr_bucket_mmap_create(mm, 0, (apr_size_t)file->finfo.size,
+ c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
+ return HTTP_INTERNAL_SERVER_ERROR;
+#endif
+ return OK;
+}
+
+static int sendfile_handler(request_rec *r, a_file *file)
+{
+#if APR_HAS_SENDFILE
+ conn_rec *c = r->connection;
+ apr_bucket *b;
+ apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ b = apr_bucket_file_create(file->file, 0, (apr_size_t)file->finfo.size,
+ r->pool, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
+ return HTTP_INTERNAL_SERVER_ERROR;
+#endif
+ return OK;
+}
+
+static int file_cache_handler(request_rec *r)
+{
+ a_file *match;
+ int errstatus;
+ int rc = OK;
+
+ /* XXX: not sure if this is right yet
+ * see comment in http_core.c:default_handler
+ */
+ if (ap_strcmp_match(r->handler, "*/*")) {
+ return DECLINED;
+ }
+
+ /* we don't handle anything but GET */
+ if (r->method_number != M_GET) return DECLINED;
+
+ /* did xlat phase find the file? */
+ match = ap_get_module_config(r->request_config, &file_cache_module);
+
+ if (match == NULL) {
+ return DECLINED;
+ }
+
+ /* note that we would handle GET on this resource */
+ r->allowed |= (AP_METHOD_BIT << M_GET);
+
+ /* This handler has no use for a request body (yet), but we still
+ * need to read and discard it if the client sent one.
+ */
+ if ((errstatus = ap_discard_request_body(r)) != OK)
+ return errstatus;
+
+ ap_update_mtime(r, match->finfo.mtime);
+
+ /* ap_set_last_modified() always converts the file mtime to a string
+ * which is slow. Accelerate the common case.
+ * ap_set_last_modified(r);
+ */
+ {
+ apr_time_t mod_time;
+ char *datestr;
+
+ mod_time = ap_rationalize_mtime(r, r->mtime);
+ if (mod_time == match->finfo.mtime)
+ datestr = match->mtimestr;
+ else {
+ datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
+ apr_rfc822_date(datestr, mod_time);
+ }
+ apr_table_setn(r->headers_out, "Last-Modified", datestr);
+ }
+
+ ap_set_etag(r);
+ if ((errstatus = ap_meets_conditions(r)) != OK) {
+ return errstatus;
+ }
+
+ /* ap_set_content_length() always converts the same number and never
+ * returns an error. Accelerate it.
+ */
+ r->clength = match->finfo.size;
+ apr_table_setn(r->headers_out, "Content-Length", match->sizestr);
+
+ /* Call appropriate handler */
+ if (!r->header_only) {
+ if (match->is_mmapped == TRUE)
+ rc = mmap_handler(r, match);
+ else
+ rc = sendfile_handler(r, match);
+ }
+
+ return rc;
+}
+
+static command_rec file_cache_cmds[] =
+{
+AP_INIT_ITERATE("cachefile", cachefilehandle, NULL, RSRC_CONF,
+ "A space separated list of files to add to the file handle cache at config time"),
+AP_INIT_ITERATE("mmapfile", cachefilemmap, NULL, RSRC_CONF,
+ "A space separated list of files to mmap at config time"),
+ {NULL}
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
+ /* This trick doesn't work apparently because the translate hooks
+ are single shot. If the core_hook returns OK, then our hook is
+ not called.
+ ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE);
+ */
+
+}
+
+module AP_MODULE_DECLARE_DATA file_cache_module =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ create_server_config, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ file_cache_cmds, /* command handlers */
+ register_hooks /* register hooks */
+};
diff --git a/modules/cache/mod_file_cache.dsp b/modules/cache/mod_file_cache.dsp
new file mode 100644
index 00000000..3af6e968
--- /dev/null
+++ b/modules/cache/mod_file_cache.dsp
@@ -0,0 +1,99 @@
+# Microsoft Developer Studio Project File - Name="mod_file_cache" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_file_cache - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_file_cache.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_file_cache.mak" CFG="mod_file_cache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_file_cache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_file_cache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_file_cache - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_file_cache_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_file_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_file_cache.so" /d "LONG_NAME=file_cache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 /nologo /subsystem:windows /dll /out:"Release/mod_file_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so
+# ADD LINK32 /nologo /subsystem:windows /dll /incremental:no /debug /out:"Release/mod_file_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so /opt:ref
+
+!ELSEIF "$(CFG)" == "mod_file_cache - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_file_cache_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_file_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_file_cache.so" /d "LONG_NAME=file_cache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_file_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so
+# ADD LINK32 /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_file_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_file_cache - Win32 Release"
+# Name "mod_file_cache - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_file_cache.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cache/mod_file_cache.exp b/modules/cache/mod_file_cache.exp
new file mode 100644
index 00000000..23b092a6
--- /dev/null
+++ b/modules/cache/mod_file_cache.exp
@@ -0,0 +1 @@
+file_cache_module
diff --git a/modules/cache/mod_mem_cache.c b/modules/cache/mod_mem_cache.c
new file mode 100644
index 00000000..b9c6915d
--- /dev/null
+++ b/modules/cache/mod_mem_cache.c
@@ -0,0 +1,1095 @@
+/* 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.
+ */
+
+/*
+ * Rules for managing obj->refcount:
+ * refcount should be incremented when an object is placed in the cache. Insertion
+ * of an object into the cache and the refcount increment should happen under
+ * protection of the sconf->lock.
+ *
+ * refcount should be decremented when the object is removed from the cache.
+ * Object should be removed from the cache and the refcount decremented while
+ * under protection of the sconf->lock.
+ *
+ * refcount should be incremented when an object is retrieved from the cache
+ * by a worker thread. The retrieval/find operation and refcount increment
+ * should occur under protection of the sconf->lock
+ *
+ * refcount can be atomically decremented w/o protection of the sconf->lock
+ * by worker threads.
+ *
+ * Any object whose refcount drops to 0 should be freed/cleaned up. A refcount
+ * of 0 means the object is not in the cache and no worker threads are accessing
+ * it.
+ */
+#define CORE_PRIVATE
+#include "mod_cache.h"
+#include "cache_pqueue.h"
+#include "cache_cache.h"
+#include "ap_provider.h"
+#include "ap_mpm.h"
+#include "apr_thread_mutex.h"
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if !APR_HAS_THREADS
+#error This module does not currently compile unless you have a thread-capable APR. Sorry!
+#endif
+
+module AP_MODULE_DECLARE_DATA mem_cache_module;
+
+typedef enum {
+ CACHE_TYPE_FILE = 1,
+ CACHE_TYPE_HEAP,
+ CACHE_TYPE_MMAP
+} cache_type_e;
+
+typedef struct {
+ char* hdr;
+ char* val;
+} cache_header_tbl_t;
+
+typedef struct mem_cache_object {
+ cache_type_e type;
+ apr_ssize_t num_header_out;
+ apr_ssize_t num_req_hdrs;
+ cache_header_tbl_t *header_out;
+ cache_header_tbl_t *req_hdrs; /* for Vary negotiation */
+ apr_size_t m_len;
+ void *m;
+ apr_os_file_t fd;
+ apr_int32_t flags; /* File open flags */
+ long priority; /**< the priority of this entry */
+ long total_refs; /**< total number of references this entry has had */
+
+ apr_uint32_t pos; /**< the position of this entry in the cache */
+
+} mem_cache_object_t;
+
+typedef struct {
+ apr_thread_mutex_t *lock;
+ cache_cache_t *cache_cache;
+
+ /* Fields set by config directives */
+ apr_size_t min_cache_object_size; /* in bytes */
+ apr_size_t max_cache_object_size; /* in bytes */
+ apr_size_t max_cache_size; /* in bytes */
+ apr_size_t max_object_cnt;
+ cache_pqueue_set_priority cache_remove_algorithm;
+
+ /* maximum amount of data to buffer on a streamed response where
+ * we haven't yet seen EOS */
+ apr_off_t max_streaming_buffer_size;
+} mem_cache_conf;
+static mem_cache_conf *sconf;
+
+#define DEFAULT_MAX_CACHE_SIZE 100*1024
+#define DEFAULT_MIN_CACHE_OBJECT_SIZE 0
+#define DEFAULT_MAX_CACHE_OBJECT_SIZE 10000
+#define DEFAULT_MAX_OBJECT_CNT 1009
+#define DEFAULT_MAX_STREAMING_BUFFER_SIZE 100000
+#define CACHEFILE_LEN 20
+
+/* Forward declarations */
+static int remove_entity(cache_handle_t *h);
+static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *i);
+static apr_status_t store_body(cache_handle_t *h, request_rec *r, apr_bucket_brigade *b);
+static apr_status_t recall_headers(cache_handle_t *h, request_rec *r);
+static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb);
+
+static void cleanup_cache_object(cache_object_t *obj);
+
+static long memcache_get_priority(void*a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+
+ return mobj->priority;
+}
+
+static void memcache_inc_frequency(void*a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+
+ mobj->total_refs++;
+ mobj->priority = 0;
+}
+
+static void memcache_set_pos(void *a, apr_ssize_t pos)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+
+ apr_atomic_set32(&mobj->pos, pos);
+}
+static apr_ssize_t memcache_get_pos(void *a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+
+ return apr_atomic_read32(&mobj->pos);
+}
+
+static apr_size_t memcache_cache_get_size(void*a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+ return mobj->m_len;
+}
+/** callback to get the key of a item */
+static const char* memcache_cache_get_key(void*a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ return obj->key;
+}
+/**
+ * memcache_cache_free()
+ * memcache_cache_free is a callback that is only invoked by a thread
+ * running in cache_insert(). cache_insert() runs under protection
+ * of sconf->lock. By the time this function has been entered, the cache_object
+ * has been ejected from the cache. decrement the refcount and if the refcount drops
+ * to 0, cleanup the cache object.
+ */
+static void memcache_cache_free(void*a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+
+ /* Decrement the refcount to account for the object being ejected
+ * from the cache. If the refcount is 0, free the object.
+ */
+ if (!apr_atomic_dec32(&obj->refcount)) {
+ cleanup_cache_object(obj);
+ }
+}
+/*
+ * functions return a 'negative' score since priority queues
+ * dequeue the object with the highest value first
+ */
+static long memcache_lru_algorithm(long queue_clock, void *a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+ if (mobj->priority == 0)
+ mobj->priority = queue_clock - mobj->total_refs;
+
+ /*
+ * a 'proper' LRU function would just be
+ * mobj->priority = mobj->total_refs;
+ */
+ return mobj->priority;
+}
+
+static long memcache_gdsf_algorithm(long queue_clock, void *a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+
+ if (mobj->priority == 0)
+ mobj->priority = queue_clock -
+ (long)(mobj->total_refs*1000 / mobj->m_len);
+
+ return mobj->priority;
+}
+
+static void cleanup_cache_object(cache_object_t *obj)
+{
+ mem_cache_object_t *mobj = obj->vobj;
+
+ /* TODO:
+ * We desperately need a more efficient way of allocating objects. We're
+ * making way too many malloc calls to create a fully populated
+ * cache object...
+ */
+
+ /* Cleanup the cache_object_t */
+ if (obj->key) {
+ free((void*)obj->key);
+ }
+
+ free(obj);
+
+ /* Cleanup the mem_cache_object_t */
+ if (mobj) {
+ if (mobj->type == CACHE_TYPE_HEAP && mobj->m) {
+ free(mobj->m);
+ }
+ if (mobj->type == CACHE_TYPE_FILE && mobj->fd) {
+#ifdef WIN32
+ CloseHandle(mobj->fd);
+#else
+ close(mobj->fd);
+#endif
+ }
+ if (mobj->header_out) {
+ if (mobj->header_out[0].hdr)
+ free(mobj->header_out[0].hdr);
+ free(mobj->header_out);
+ }
+ if (mobj->req_hdrs) {
+ if (mobj->req_hdrs[0].hdr)
+ free(mobj->req_hdrs[0].hdr);
+ free(mobj->req_hdrs);
+ }
+ free(mobj);
+ }
+}
+static apr_status_t decrement_refcount(void *arg)
+{
+ cache_object_t *obj = (cache_object_t *) arg;
+
+ /* If obj->complete is not set, the cache update failed and the
+ * object needs to be removed from the cache then cleaned up.
+ * The garbage collector may have ejected the object from the
+ * cache already, so make sure it is really still in the cache
+ * before attempting to remove it.
+ */
+ if (!obj->complete) {
+ cache_object_t *tobj = NULL;
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+ tobj = cache_find(sconf->cache_cache, obj->key);
+ if (tobj == obj) {
+ cache_remove(sconf->cache_cache, obj);
+ apr_atomic_dec32(&obj->refcount);
+ }
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+ }
+
+ /* If the refcount drops to 0, cleanup the cache object */
+ if (!apr_atomic_dec32(&obj->refcount)) {
+ cleanup_cache_object(obj);
+ }
+ return APR_SUCCESS;
+}
+static apr_status_t cleanup_cache_mem(void *sconfv)
+{
+ cache_object_t *obj;
+ mem_cache_conf *co = (mem_cache_conf*) sconfv;
+
+ if (!co) {
+ return APR_SUCCESS;
+ }
+ if (!co->cache_cache) {
+ return APR_SUCCESS;
+ }
+
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+ obj = cache_pop(co->cache_cache);
+ while (obj) {
+ /* Iterate over the cache and clean up each unreferenced entry */
+ if (!apr_atomic_dec32(&obj->refcount)) {
+ cleanup_cache_object(obj);
+ }
+ obj = cache_pop(co->cache_cache);
+ }
+
+ /* Cache is empty, free the cache table */
+ cache_free(co->cache_cache);
+
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+ return APR_SUCCESS;
+}
+/*
+ * TODO: enable directives to be overridden in various containers
+ */
+static void *create_cache_config(apr_pool_t *p, server_rec *s)
+{
+ sconf = apr_pcalloc(p, sizeof(mem_cache_conf));
+
+ sconf->min_cache_object_size = DEFAULT_MIN_CACHE_OBJECT_SIZE;
+ sconf->max_cache_object_size = DEFAULT_MAX_CACHE_OBJECT_SIZE;
+ /* Number of objects in the cache */
+ sconf->max_object_cnt = DEFAULT_MAX_OBJECT_CNT;
+ /* Size of the cache in bytes */
+ sconf->max_cache_size = DEFAULT_MAX_CACHE_SIZE;
+ sconf->cache_cache = NULL;
+ sconf->cache_remove_algorithm = memcache_gdsf_algorithm;
+ sconf->max_streaming_buffer_size = DEFAULT_MAX_STREAMING_BUFFER_SIZE;
+
+ return sconf;
+}
+
+static int create_entity(cache_handle_t *h, cache_type_e type_e,
+ request_rec *r, const char *key, apr_off_t len)
+{
+ cache_object_t *obj, *tmp_obj;
+ mem_cache_object_t *mobj;
+ apr_size_t key_len;
+
+ if (len == -1) {
+ /* Caching a streaming response. Assume the response is
+ * less than or equal to max_streaming_buffer_size. We will
+ * correct all the cache size counters in store_body once
+ * we know exactly know how much we are caching.
+ */
+ len = sconf->max_streaming_buffer_size;
+ }
+
+ /* Note: cache_insert() will automatically garbage collect
+ * objects from the cache if the max_cache_size threshold is
+ * exceeded. This means mod_mem_cache does not need to implement
+ * max_cache_size checks.
+ */
+ if (len < sconf->min_cache_object_size ||
+ len > sconf->max_cache_object_size) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "mem_cache: URL %s failed the size check and will not be cached.",
+ key);
+ return DECLINED;
+ }
+
+ if (type_e == CACHE_TYPE_FILE) {
+ /* CACHE_TYPE_FILE is only valid for local content handled by the
+ * default handler. Need a better way to check if the file is
+ * local or not.
+ */
+ if (!r->filename) {
+ return DECLINED;
+ }
+ }
+
+ /* Allocate and initialize cache_object_t */
+ obj = calloc(1, sizeof(*obj));
+ if (!obj) {
+ return DECLINED;
+ }
+ key_len = strlen(key) + 1;
+ obj->key = malloc(key_len);
+ if (!obj->key) {
+ cleanup_cache_object(obj);
+ return DECLINED;
+ }
+ memcpy((void*)obj->key, key, key_len);
+
+ /* Allocate and init mem_cache_object_t */
+ mobj = calloc(1, sizeof(*mobj));
+ if (!mobj) {
+ cleanup_cache_object(obj);
+ return DECLINED;
+ }
+
+ /* Finish initing the cache object */
+ apr_atomic_set32(&obj->refcount, 1);
+ mobj->total_refs = 1;
+ obj->complete = 0;
+ obj->vobj = mobj;
+ /* Safe cast: We tested < sconf->max_cache_object_size above */
+ mobj->m_len = (apr_size_t)len;
+ mobj->type = type_e;
+
+ /* Place the cache_object_t into the hash table.
+ * Note: Perhaps we should wait to put the object in the
+ * hash table when the object is complete? I add the object here to
+ * avoid multiple threads attempting to cache the same content only
+ * to discover at the very end that only one of them will succeed.
+ * Furthermore, adding the cache object to the table at the end could
+ * open up a subtle but easy to exploit DoS hole: someone could request
+ * a very large file with multiple requests. Better to detect this here
+ * rather than after the cache object has been completely built and
+ * initialized...
+ * XXX Need a way to insert into the cache w/o such coarse grained locking
+ */
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+ tmp_obj = (cache_object_t *) cache_find(sconf->cache_cache, key);
+
+ if (!tmp_obj) {
+ cache_insert(sconf->cache_cache, obj);
+ /* Add a refcount to account for the reference by the
+ * hashtable in the cache. Refcount should be 2 now, one
+ * for this thread, and one for the cache.
+ */
+ apr_atomic_inc32(&obj->refcount);
+ }
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+
+ if (tmp_obj) {
+ /* This thread collided with another thread loading the same object
+ * into the cache at the same time. Defer to the other thread which
+ * is further along.
+ */
+ cleanup_cache_object(obj);
+ return DECLINED;
+ }
+
+ apr_pool_cleanup_register(r->pool, obj, decrement_refcount,
+ apr_pool_cleanup_null);
+
+ /* Populate the cache handle */
+ h->cache_obj = obj;
+
+ return OK;
+}
+
+static int create_mem_entity(cache_handle_t *h, request_rec *r,
+ const char *key, apr_off_t len)
+{
+ return create_entity(h, CACHE_TYPE_HEAP, r, key, len);
+}
+
+static int create_fd_entity(cache_handle_t *h, request_rec *r,
+ const char *key, apr_off_t len)
+{
+ return create_entity(h, CACHE_TYPE_FILE, r, key, len);
+}
+
+static int open_entity(cache_handle_t *h, request_rec *r, const char *key)
+{
+ cache_object_t *obj;
+
+ /* Look up entity keyed to 'url' */
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+ obj = (cache_object_t *) cache_find(sconf->cache_cache, key);
+ if (obj) {
+ if (obj->complete) {
+ request_rec *rmain=r, *rtmp;
+ apr_atomic_inc32(&obj->refcount);
+ /* cache is worried about overall counts, not 'open' ones */
+ cache_update(sconf->cache_cache, obj);
+
+ /* If this is a subrequest, register the cleanup against
+ * the main request. This will prevent the cache object
+ * from being cleaned up from under the request after the
+ * subrequest is destroyed.
+ */
+ rtmp = r;
+ while (rtmp) {
+ rmain = rtmp;
+ rtmp = rmain->main;
+ }
+ apr_pool_cleanup_register(rmain->pool, obj, decrement_refcount,
+ apr_pool_cleanup_null);
+ }
+ else {
+ obj = NULL;
+ }
+ }
+
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+
+ if (!obj) {
+ return DECLINED;
+ }
+
+ /* Initialize the cache_handle */
+ h->cache_obj = obj;
+ h->req_hdrs = NULL; /* Pick these up in recall_headers() */
+ return OK;
+}
+
+/* remove_entity()
+ * Notes:
+ * refcount should be at least 1 upon entry to this function to account
+ * for this thread's reference to the object. If the refcount is 1, then
+ * object has been removed from the cache by another thread and this thread
+ * is the last thread accessing the object.
+ */
+static int remove_entity(cache_handle_t *h)
+{
+ cache_object_t *obj = h->cache_obj;
+ cache_object_t *tobj = NULL;
+
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+
+ /* If the entity is still in the cache, remove it and decrement the
+ * refcount. If the entity is not in the cache, do nothing. In both cases
+ * decrement_refcount called by the last thread referencing the object will
+ * trigger the cleanup.
+ */
+ tobj = cache_find(sconf->cache_cache, obj->key);
+ if (tobj == obj) {
+ cache_remove(sconf->cache_cache, obj);
+ apr_atomic_dec32(&obj->refcount);
+ }
+
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+
+ return OK;
+}
+static apr_status_t serialize_table(cache_header_tbl_t **obj,
+ apr_ssize_t *nelts,
+ apr_table_t *table)
+{
+ const apr_array_header_t *elts_arr = apr_table_elts(table);
+ apr_table_entry_t *elts = (apr_table_entry_t *) elts_arr->elts;
+ apr_ssize_t i;
+ apr_size_t len = 0;
+ apr_size_t idx = 0;
+ char *buf;
+
+ *nelts = elts_arr->nelts;
+ if (*nelts == 0 ) {
+ *obj=NULL;
+ return APR_SUCCESS;
+ }
+ *obj = malloc(sizeof(cache_header_tbl_t) * elts_arr->nelts);
+ if (NULL == *obj) {
+ return APR_ENOMEM;
+ }
+ for (i = 0; i < elts_arr->nelts; ++i) {
+ len += strlen(elts[i].key);
+ len += strlen(elts[i].val);
+ len += 2; /* Extra space for NULL string terminator for key and val */
+ }
+
+ /* Transfer the headers into a contiguous memory block */
+ buf = malloc(len);
+ if (!buf) {
+ *obj = NULL;
+ return APR_ENOMEM;
+ }
+
+ for (i = 0; i < *nelts; ++i) {
+ (*obj)[i].hdr = &buf[idx];
+ len = strlen(elts[i].key) + 1; /* Include NULL terminator */
+ memcpy(&buf[idx], elts[i].key, len);
+ idx+=len;
+
+ (*obj)[i].val = &buf[idx];
+ len = strlen(elts[i].val) + 1;
+ memcpy(&buf[idx], elts[i].val, len);
+ idx+=len;
+ }
+ return APR_SUCCESS;
+}
+static int unserialize_table( cache_header_tbl_t *ctbl,
+ int num_headers,
+ apr_table_t *t )
+{
+ int i;
+
+ for (i = 0; i < num_headers; ++i) {
+ apr_table_addn(t, ctbl[i].hdr, ctbl[i].val);
+ }
+
+ return APR_SUCCESS;
+}
+/* Define request processing hook handlers */
+/* remove_url()
+ * Notes:
+ */
+static int remove_url(cache_handle_t *h, apr_pool_t *p)
+{
+ cache_object_t *obj;
+ int cleanup = 0;
+
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+
+ obj = h->cache_obj;
+ if (obj) {
+ cache_remove(sconf->cache_cache, obj);
+ /* For performance, cleanup cache object after releasing the lock */
+ cleanup = !apr_atomic_dec32(&obj->refcount);
+ }
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+
+ if (cleanup) {
+ cleanup_cache_object(obj);
+ }
+
+ return OK;
+}
+
+static apr_status_t recall_headers(cache_handle_t *h, request_rec *r)
+{
+ int rc;
+ mem_cache_object_t *mobj = (mem_cache_object_t*) h->cache_obj->vobj;
+
+ h->req_hdrs = apr_table_make(r->pool, mobj->num_req_hdrs);
+ h->resp_hdrs = apr_table_make(r->pool, mobj->num_header_out);
+
+ rc = unserialize_table(mobj->req_hdrs, mobj->num_req_hdrs, h->req_hdrs);
+ rc = unserialize_table(mobj->header_out, mobj->num_header_out,
+ h->resp_hdrs);
+
+ return rc;
+}
+
+static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+ mem_cache_object_t *mobj = (mem_cache_object_t*) h->cache_obj->vobj;
+
+ if (mobj->type == CACHE_TYPE_FILE) {
+ /* CACHE_TYPE_FILE */
+ apr_file_t *file;
+ apr_os_file_put(&file, &mobj->fd, mobj->flags, p);
+ b = apr_bucket_file_create(file, 0, mobj->m_len, p, bb->bucket_alloc);
+ }
+ else {
+ /* CACHE_TYPE_HEAP */
+ b = apr_bucket_immortal_create(mobj->m, mobj->m_len, bb->bucket_alloc);
+ }
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ b = apr_bucket_eos_create(bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ return APR_SUCCESS;
+}
+
+
+static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *info)
+{
+ cache_object_t *obj = h->cache_obj;
+ mem_cache_object_t *mobj = (mem_cache_object_t*) obj->vobj;
+ int rc;
+ apr_table_t *headers_out;
+
+ /*
+ * The cache needs to keep track of the following information:
+ * - Date, LastMod, Version, ReqTime, RespTime, ContentLength
+ * - The original request headers (for Vary)
+ * - The original response headers (for returning with a cached response)
+ * - The body of the message
+ */
+ rc = serialize_table(&mobj->req_hdrs,
+ &mobj->num_req_hdrs,
+ r->headers_in);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+
+ /* Precompute how much storage we need to hold the headers */
+ headers_out = ap_cache_cacheable_hdrs_out(r->pool, r->headers_out,
+ r->server);
+
+ /* If not set in headers_out, set Content-Type */
+ if (!apr_table_get(headers_out, "Content-Type")
+ && r->content_type) {
+ apr_table_setn(headers_out, "Content-Type",
+ ap_make_content_type(r, r->content_type));
+ }
+
+ headers_out = apr_table_overlay(r->pool, headers_out, r->err_headers_out);
+
+ rc = serialize_table(&mobj->header_out, &mobj->num_header_out,
+ headers_out);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+
+ /* Init the info struct */
+ obj->info.status = info->status;
+ if (info->date) {
+ obj->info.date = info->date;
+ }
+ if (info->response_time) {
+ obj->info.response_time = info->response_time;
+ }
+ if (info->request_time) {
+ obj->info.request_time = info->request_time;
+ }
+ if (info->expire) {
+ obj->info.expire = info->expire;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t store_body(cache_handle_t *h, request_rec *r, apr_bucket_brigade *b)
+{
+ apr_status_t rv;
+ cache_object_t *obj = h->cache_obj;
+ cache_object_t *tobj = NULL;
+ mem_cache_object_t *mobj = (mem_cache_object_t*) obj->vobj;
+ apr_read_type_e eblock = APR_BLOCK_READ;
+ apr_bucket *e;
+ char *cur;
+ int eos = 0;
+
+ if (mobj->type == CACHE_TYPE_FILE) {
+ apr_file_t *file = NULL;
+ int fd = 0;
+ int other = 0;
+
+ /* We can cache an open file descriptor if:
+ * - the brigade contains one and only one file_bucket &&
+ * - the brigade is complete &&
+ * - the file_bucket is the last data bucket in the brigade
+ */
+ for (e = APR_BRIGADE_FIRST(b);
+ e != APR_BRIGADE_SENTINEL(b);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (APR_BUCKET_IS_EOS(e)) {
+ eos = 1;
+ }
+ else if (APR_BUCKET_IS_FILE(e)) {
+ apr_bucket_file *a = e->data;
+ fd++;
+ file = a->fd;
+ }
+ else {
+ other++;
+ }
+ }
+ if (fd == 1 && !other && eos) {
+ apr_file_t *tmpfile;
+ const char *name;
+ /* Open a new XTHREAD handle to the file */
+ apr_file_name_get(&name, file);
+ mobj->flags = ((APR_SENDFILE_ENABLED & apr_file_flags_get(file))
+ | APR_READ | APR_BINARY | APR_XTHREAD | APR_FILE_NOCLEANUP);
+ rv = apr_file_open(&tmpfile, name, mobj->flags,
+ APR_OS_DEFAULT, r->pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ apr_file_inherit_unset(tmpfile);
+ apr_os_file_get(&(mobj->fd), tmpfile);
+
+ /* Open for business */
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
+ "mem_cache: Cached file: %s with key: %s", name, obj->key);
+ obj->complete = 1;
+ return APR_SUCCESS;
+ }
+
+ /* Content not suitable for fd caching. Cache in-memory instead. */
+ mobj->type = CACHE_TYPE_HEAP;
+ }
+
+ /*
+ * FD cacheing is not enabled or the content was not
+ * suitable for fd caching.
+ */
+ if (mobj->m == NULL) {
+ mobj->m = malloc(mobj->m_len);
+ if (mobj->m == NULL) {
+ return APR_ENOMEM;
+ }
+ obj->count = 0;
+ }
+ cur = (char*) mobj->m + obj->count;
+
+ /* Iterate accross the brigade and populate the cache storage */
+ for (e = APR_BRIGADE_FIRST(b);
+ e != APR_BRIGADE_SENTINEL(b);
+ e = APR_BUCKET_NEXT(e))
+ {
+ const char *s;
+ apr_size_t len;
+
+ if (APR_BUCKET_IS_EOS(e)) {
+ if (mobj->m_len > obj->count) {
+ /* Caching a streamed response. Reallocate a buffer of the
+ * correct size and copy the streamed response into that
+ * buffer */
+ char *buf = malloc(obj->count);
+ if (!buf) {
+ return APR_ENOMEM;
+ }
+ memcpy(buf, mobj->m, obj->count);
+ free(mobj->m);
+ mobj->m = buf;
+
+ /* Now comes the crufty part... there is no way to tell the
+ * cache that the size of the object has changed. We need
+ * to remove the object, update the size and re-add the
+ * object, all under protection of the lock.
+ */
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+ /* Has the object been ejected from the cache?
+ */
+ tobj = (cache_object_t *) cache_find(sconf->cache_cache, obj->key);
+ if (tobj == obj) {
+ /* Object is still in the cache, remove it, update the len field then
+ * replace it under protection of sconf->lock.
+ */
+ cache_remove(sconf->cache_cache, obj);
+ /* For illustration, cache no longer has reference to the object
+ * so decrement the refcount
+ * apr_atomic_dec32(&obj->refcount);
+ */
+ mobj->m_len = obj->count;
+
+ cache_insert(sconf->cache_cache, obj);
+ /* For illustration, cache now has reference to the object, so
+ * increment the refcount
+ * apr_atomic_inc32(&obj->refcount);
+ */
+ }
+ else if (tobj) {
+ /* Different object with the same key found in the cache. Doing nothing
+ * here will cause the object refcount to drop to 0 in decrement_refcount
+ * and the object will be cleaned up.
+ */
+
+ } else {
+ /* Object has been ejected from the cache, add it back to the cache */
+ mobj->m_len = obj->count;
+ cache_insert(sconf->cache_cache, obj);
+ apr_atomic_inc32(&obj->refcount);
+ }
+
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+ }
+ /* Open for business */
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
+ "mem_cache: Cached url: %s", obj->key);
+ obj->complete = 1;
+ break;
+ }
+ rv = apr_bucket_read(e, &s, &len, eblock);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ if (len) {
+ /* Check for buffer overflow */
+ if ((obj->count + len) > mobj->m_len) {
+ return APR_ENOMEM;
+ }
+ else {
+ memcpy(cur, s, len);
+ cur+=len;
+ obj->count+=len;
+ }
+ }
+ /* This should not fail, but if it does, we are in BIG trouble
+ * cause we just stomped all over the heap.
+ */
+ AP_DEBUG_ASSERT(obj->count <= mobj->m_len);
+ }
+ return APR_SUCCESS;
+}
+/**
+ * Configuration and start-up
+ */
+static int mem_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ int threaded_mpm;
+
+ /* Sanity check the cache configuration */
+ if (sconf->min_cache_object_size >= sconf->max_cache_object_size) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s,
+ "MCacheMaxObjectSize must be greater than MCacheMinObjectSize");
+ return DONE;
+ }
+ if (sconf->max_cache_object_size >= sconf->max_cache_size) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s,
+ "MCacheSize must be greater than MCacheMaxObjectSize");
+ return DONE;
+ }
+ if (sconf->max_streaming_buffer_size > sconf->max_cache_object_size) {
+ /* Issue a notice only if something other than the default config
+ * is being used */
+ if (sconf->max_streaming_buffer_size != DEFAULT_MAX_STREAMING_BUFFER_SIZE &&
+ sconf->max_cache_object_size != DEFAULT_MAX_CACHE_OBJECT_SIZE) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s,
+ "MCacheMaxStreamingBuffer must be less than or equal to MCacheMaxObjectSize. "
+ "Resetting MCacheMaxStreamingBuffer to MCacheMaxObjectSize.");
+ }
+ sconf->max_streaming_buffer_size = sconf->max_cache_object_size;
+ }
+ if (sconf->max_streaming_buffer_size < sconf->min_cache_object_size) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
+ "MCacheMaxStreamingBuffer must be greater than or equal to MCacheMinObjectSize. "
+ "Resetting MCacheMaxStreamingBuffer to MCacheMinObjectSize.");
+ sconf->max_streaming_buffer_size = sconf->min_cache_object_size;
+ }
+ ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded_mpm);
+ if (threaded_mpm) {
+ apr_thread_mutex_create(&sconf->lock, APR_THREAD_MUTEX_DEFAULT, p);
+ }
+
+ sconf->cache_cache = cache_init(sconf->max_object_cnt,
+ sconf->max_cache_size,
+ memcache_get_priority,
+ sconf->cache_remove_algorithm,
+ memcache_get_pos,
+ memcache_set_pos,
+ memcache_inc_frequency,
+ memcache_cache_get_size,
+ memcache_cache_get_key,
+ memcache_cache_free);
+ apr_pool_cleanup_register(p, sconf, cleanup_cache_mem, apr_pool_cleanup_null);
+
+ if (sconf->cache_cache)
+ return OK;
+
+ return -1;
+
+}
+
+static const char
+*set_max_cache_size(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ apr_size_t val;
+
+ if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) {
+ return "MCacheSize argument must be an integer representing the max cache size in KBytes.";
+ }
+ sconf->max_cache_size = val*1024;
+ return NULL;
+}
+static const char
+*set_min_cache_object_size(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ apr_size_t val;
+
+ if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) {
+ return "MCacheMinObjectSize value must be an integer (bytes)";
+ }
+ sconf->min_cache_object_size = val;
+ return NULL;
+}
+static const char
+*set_max_cache_object_size(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ apr_size_t val;
+
+ if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) {
+ return "MCacheMaxObjectSize value must be an integer (bytes)";
+ }
+ sconf->max_cache_object_size = val;
+ return NULL;
+}
+static const char
+*set_max_object_count(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ apr_size_t val;
+
+ if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) {
+ return "MCacheMaxObjectCount value must be an integer";
+ }
+ sconf->max_object_cnt = val;
+ return NULL;
+}
+
+static const char
+*set_cache_removal_algorithm(cmd_parms *parms, void *name, const char *arg)
+{
+ if (strcasecmp("LRU", arg)) {
+ sconf->cache_remove_algorithm = memcache_lru_algorithm;
+ }
+ else {
+ if (strcasecmp("GDSF", arg)) {
+ sconf->cache_remove_algorithm = memcache_gdsf_algorithm;
+ }
+ else {
+ return "currently implemented algorithms are LRU and GDSF";
+ }
+ }
+ return NULL;
+}
+
+static const char *set_max_streaming_buffer(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ char *err;
+ if (apr_strtoff(&sconf->max_streaming_buffer_size, arg, &err, 10) || *err) {
+ return "MCacheMaxStreamingBuffer value must be a number";
+ }
+
+ return NULL;
+}
+
+static const command_rec cache_cmds[] =
+{
+ AP_INIT_TAKE1("MCacheSize", set_max_cache_size, NULL, RSRC_CONF,
+ "The maximum amount of memory used by the cache in KBytes"),
+ AP_INIT_TAKE1("MCacheMaxObjectCount", set_max_object_count, NULL, RSRC_CONF,
+ "The maximum number of objects allowed to be placed in the cache"),
+ AP_INIT_TAKE1("MCacheMinObjectSize", set_min_cache_object_size, NULL, RSRC_CONF,
+ "The minimum size (in bytes) of an object to be placed in the cache"),
+ AP_INIT_TAKE1("MCacheMaxObjectSize", set_max_cache_object_size, NULL, RSRC_CONF,
+ "The maximum size (in bytes) of an object to be placed in the cache"),
+ AP_INIT_TAKE1("MCacheRemovalAlgorithm", set_cache_removal_algorithm, NULL, RSRC_CONF,
+ "The algorithm used to remove entries from the cache (default: GDSF)"),
+ AP_INIT_TAKE1("MCacheMaxStreamingBuffer", set_max_streaming_buffer, NULL, RSRC_CONF,
+ "Maximum number of bytes of content to buffer for a streamed response"),
+ {NULL}
+};
+
+static const cache_provider cache_mem_provider =
+{
+ &remove_entity,
+ &store_headers,
+ &store_body,
+ &recall_headers,
+ &recall_body,
+ &create_mem_entity,
+ &open_entity,
+ &remove_url,
+};
+
+static const cache_provider cache_fd_provider =
+{
+ &remove_entity,
+ &store_headers,
+ &store_body,
+ &recall_headers,
+ &recall_body,
+ &create_fd_entity,
+ &open_entity,
+ &remove_url,
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_post_config(mem_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ /* cache initializer */
+ /* cache_hook_init(cache_mem_init, NULL, NULL, APR_HOOK_MIDDLE); */
+ /*
+ cache_hook_create_entity(create_entity, NULL, NULL, APR_HOOK_MIDDLE);
+ cache_hook_open_entity(open_entity, NULL, NULL, APR_HOOK_MIDDLE);
+ cache_hook_remove_url(remove_url, NULL, NULL, APR_HOOK_MIDDLE);
+ */
+ ap_register_provider(p, CACHE_PROVIDER_GROUP, "mem", "0",
+ &cache_mem_provider);
+ ap_register_provider(p, CACHE_PROVIDER_GROUP, "fd", "0",
+ &cache_fd_provider);
+}
+
+module AP_MODULE_DECLARE_DATA mem_cache_module =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ create_cache_config, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ cache_cmds, /* command apr_table_t */
+ register_hooks
+};
diff --git a/modules/cache/mod_mem_cache.dsp b/modules/cache/mod_mem_cache.dsp
new file mode 100644
index 00000000..7b77743f
--- /dev/null
+++ b/modules/cache/mod_mem_cache.dsp
@@ -0,0 +1,103 @@
+# Microsoft Developer Studio Project File - Name="mod_mem_cache" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_mem_cache - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_mem_cache.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_mem_cache.mak" CFG="mod_mem_cache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_mem_cache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_mem_cache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_mem_cache - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "mod_mem_cache_EXPORTS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_mem_cache_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_mem_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_mem_cache.so" /d "LONG_NAME=mem_cache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Release/mod_mem_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_mem_cache.so /opt:ref
+
+!ELSEIF "$(CFG)" == "mod_mem_cache - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_mem_cache_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_mem_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_mem_cache.so" /d "LONG_NAME=mem_cache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_mem_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_mem_cache.so
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_mem_cache - Win32 Release"
+# Name "mod_mem_cache - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_cache.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_mem_cache.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project