1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "mod_cache.h"
18
19#include "cache_storage.h"
20#include "cache_util.h"
21
22APLOG_USE_MODULE(cache);
23
24extern APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key;
25
26extern module AP_MODULE_DECLARE_DATA cache_module;
27
28/* -------------------------------------------------------------- */
29
30/*
31 * delete all URL entities from the cache
32 *
33 */
34int cache_remove_url(cache_request_rec *cache, request_rec *r)
35{
36    cache_provider_list *list;
37    cache_handle_t *h;
38
39    list = cache->providers;
40
41    /* Remove the stale cache entry if present. If not, we're
42     * being called from outside of a request; remove the
43     * non-stale handle.
44     */
45    h = cache->stale_handle ? cache->stale_handle : cache->handle;
46    if (!h) {
47       return OK;
48    }
49    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00691)
50                 "cache: Removing url %s from the cache", h->cache_obj->key);
51
52    /* for each specified cache type, delete the URL */
53    while(list) {
54        list->provider->remove_url(h, r);
55        list = list->next;
56    }
57    return OK;
58}
59
60
61/*
62 * create a new URL entity in the cache
63 *
64 * It is possible to store more than once entity per URL. This
65 * function will always create a new entity, regardless of whether
66 * other entities already exist for the same URL.
67 *
68 * The size of the entity is provided so that a cache module can
69 * decide whether or not it wants to cache this particular entity.
70 * If the size is unknown, a size of -1 should be set.
71 */
72int cache_create_entity(cache_request_rec *cache, request_rec *r,
73                        apr_off_t size, apr_bucket_brigade *in)
74{
75    cache_provider_list *list;
76    cache_handle_t *h = apr_pcalloc(r->pool, sizeof(cache_handle_t));
77    apr_status_t rv;
78
79    if (!cache) {
80        /* This should never happen */
81        ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00692)
82                "cache: No cache request information available for key"
83                " generation");
84        return APR_EGENERAL;
85    }
86
87    if (!cache->key) {
88        rv = cache_generate_key(r, r->pool, &cache->key);
89        if (rv != APR_SUCCESS) {
90            return rv;
91        }
92    }
93
94    list = cache->providers;
95    /* for each specified cache type, delete the URL */
96    while (list) {
97        switch (rv = list->provider->create_entity(h, r, cache->key, size, in)) {
98        case OK: {
99            cache->handle = h;
100            cache->provider = list->provider;
101            cache->provider_name = list->provider_name;
102            return OK;
103        }
104        case DECLINED: {
105            list = list->next;
106            continue;
107        }
108        default: {
109            return rv;
110        }
111        }
112    }
113    return DECLINED;
114}
115
116static int filter_header_do(void *v, const char *key, const char *val)
117{
118    if ((*key == 'W' || *key == 'w') && !strcasecmp(key, "Warning")
119            && *val == '1') {
120        /* any stored Warning headers with warn-code 1xx (see section
121         * 14.46) MUST be deleted from the cache entry and the forwarded
122         * response.
123         */
124    }
125    else {
126        apr_table_addn(v, key, val);
127    }
128    return 1;
129}
130static int remove_header_do(void *v, const char *key, const char *val)
131{
132    if ((*key == 'W' || *key == 'w') && !strcasecmp(key, "Warning")) {
133        /* any stored Warning headers with warn-code 2xx MUST be retained
134         * in the cache entry and the forwarded response.
135         */
136    }
137    else {
138        apr_table_unset(v, key);
139    }
140    return 1;
141}
142static int add_header_do(void *v, const char *key, const char *val)
143{
144    apr_table_addn(v, key, val);
145    return 1;
146}
147
148/**
149 * Take two sets of headers, sandwich them together, and apply the result to
150 * r->headers_out.
151 *
152 * To complicate this, a header may be duplicated in either table. Should a
153 * header exist in the top table, all matching headers will be removed from
154 * the bottom table before the headers are combined. The Warning headers are
155 * handled specially. Warnings are added rather than being replaced, while
156 * in the case of revalidation 1xx Warnings are stripped.
157 *
158 * The Content-Type and Last-Modified headers are then re-parsed and inserted
159 * into the request.
160 */
161void cache_accept_headers(cache_handle_t *h, request_rec *r, apr_table_t *top,
162        apr_table_t *bottom, int revalidation)
163{
164    const char *v;
165
166    if (revalidation) {
167        r->headers_out = apr_table_make(r->pool, 10);
168        apr_table_do(filter_header_do, r->headers_out, bottom, NULL);
169    }
170    else if (r->headers_out != bottom) {
171        r->headers_out = apr_table_copy(r->pool, bottom);
172    }
173    apr_table_do(remove_header_do, r->headers_out, top, NULL);
174    apr_table_do(add_header_do, r->headers_out, top, NULL);
175
176    v = apr_table_get(r->headers_out, "Content-Type");
177    if (v) {
178        ap_set_content_type(r, v);
179        /*
180         * Also unset possible Content-Type headers in r->headers_out and
181         * r->err_headers_out as they may be different to what we have received
182         * from the cache.
183         * Actually they are not needed as r->content_type set by
184         * ap_set_content_type above will be used in the store_headers functions
185         * of the storage providers as a fallback and the HTTP_HEADER filter
186         * does overwrite the Content-Type header with r->content_type anyway.
187         */
188        apr_table_unset(r->headers_out, "Content-Type");
189        apr_table_unset(r->err_headers_out, "Content-Type");
190    }
191
192    /* If the cache gave us a Last-Modified header, we can't just
193     * pass it on blindly because of restrictions on future values.
194     */
195    v = apr_table_get(r->headers_out, "Last-Modified");
196    if (v) {
197        ap_update_mtime(r, apr_date_parse_http(v));
198        ap_set_last_modified(r);
199    }
200
201}
202
203/*
204 * select a specific URL entity in the cache
205 *
206 * It is possible to store more than one entity per URL. Content
207 * negotiation is used to select an entity. Once an entity is
208 * selected, details of it are stored in the per request
209 * config to save time when serving the request later.
210 *
211 * This function returns OK if successful, DECLINED if no
212 * cached entity fits the bill.
213 */
214int cache_select(cache_request_rec *cache, request_rec *r)
215{
216    cache_provider_list *list;
217    apr_status_t rv;
218    cache_handle_t *h;
219
220    if (!cache) {
221        /* This should never happen */
222        ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00693)
223                "cache: No cache request information available for key"
224                " generation");
225        return DECLINED;
226    }
227
228    /* if no-cache, we can't serve from the cache, but we may store to the
229     * cache.
230     */
231    if (!ap_cache_check_no_cache(cache, r)) {
232        return DECLINED;
233    }
234
235    if (!cache->key) {
236        rv = cache_generate_key(r, r->pool, &cache->key);
237        if (rv != APR_SUCCESS) {
238            return DECLINED;
239        }
240    }
241
242    /* go through the cache types till we get a match */
243    h = apr_palloc(r->pool, sizeof(cache_handle_t));
244
245    list = cache->providers;
246
247    while (list) {
248        switch ((rv = list->provider->open_entity(h, r, cache->key))) {
249        case OK: {
250            char *vary = NULL;
251            int mismatch = 0;
252            char *last = NULL;
253
254            if (list->provider->recall_headers(h, r) != APR_SUCCESS) {
255                /* try again with next cache type */
256                list = list->next;
257                continue;
258            }
259
260            /*
261             * Check Content-Negotiation - Vary
262             *
263             * At this point we need to make sure that the object we found in
264             * the cache is the same object that would be delivered to the
265             * client, when the effects of content negotiation are taken into
266             * effect.
267             *
268             * In plain english, we want to make sure that a language-negotiated
269             * document in one language is not given to a client asking for a
270             * language negotiated document in a different language by mistake.
271             *
272             * This code makes the assumption that the storage manager will
273             * cache the req_hdrs if the response contains a Vary
274             * header.
275             *
276             * RFC2616 13.6 and 14.44 describe the Vary mechanism.
277             */
278            vary = cache_strqtok(
279                    apr_pstrdup(r->pool,
280                            cache_table_getm(r->pool, h->resp_hdrs, "Vary")),
281                    CACHE_SEPARATOR, &last);
282            while (vary) {
283                const char *h1, *h2;
284
285                /*
286                 * is this header in the request and the header in the cached
287                 * request identical? If not, we give up and do a straight get
288                 */
289                h1 = cache_table_getm(r->pool, r->headers_in, vary);
290                h2 = cache_table_getm(r->pool, h->req_hdrs, vary);
291                if (h1 == h2) {
292                    /* both headers NULL, so a match - do nothing */
293                }
294                else if (h1 && h2 && !strcmp(h1, h2)) {
295                    /* both headers exist and are equal - do nothing */
296                }
297                else {
298                    /* headers do not match, so Vary failed */
299                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
300                            r, APLOGNO(00694) "cache_select(): Vary header mismatch.");
301                    mismatch = 1;
302                    break;
303                }
304                vary = cache_strqtok(NULL, CACHE_SEPARATOR, &last);
305            }
306
307            /* no vary match, try next provider */
308            if (mismatch) {
309                /* try again with next cache type */
310                list = list->next;
311                continue;
312            }
313
314            cache->provider = list->provider;
315            cache->provider_name = list->provider_name;
316
317            /*
318             * RFC2616 13.3.4 Rules for When to Use Entity Tags and Last-Modified
319             * Dates: An HTTP/1.1 caching proxy, upon receiving a conditional request
320             * that includes both a Last-Modified date and one or more entity tags as
321             * cache validators, MUST NOT return a locally cached response to the
322             * client unless that cached response is consistent with all of the
323             * conditional header fields in the request.
324             */
325            if (ap_condition_if_match(r, h->resp_hdrs) == AP_CONDITION_NOMATCH
326                    || ap_condition_if_unmodified_since(r, h->resp_hdrs)
327                            == AP_CONDITION_NOMATCH
328                    || ap_condition_if_none_match(r, h->resp_hdrs)
329                            == AP_CONDITION_NOMATCH
330                    || ap_condition_if_modified_since(r, h->resp_hdrs)
331                            == AP_CONDITION_NOMATCH
332                    || ap_condition_if_range(r, h->resp_hdrs) == AP_CONDITION_NOMATCH) {
333                mismatch = 1;
334            }
335
336            /* Is our cached response fresh enough? */
337            if (mismatch || !cache_check_freshness(h, cache, r)) {
338                const char *etag, *lastmod;
339
340                /* Cache-Control: only-if-cached and revalidation required, try
341                 * the next provider
342                 */
343                if (cache->control_in.only_if_cached) {
344                    /* try again with next cache type */
345                    list = list->next;
346                    continue;
347                }
348
349                /* set aside the stale entry for accessing later */
350                cache->stale_headers = apr_table_copy(r->pool,
351                        r->headers_in);
352                cache->stale_handle = h;
353
354                /* if no existing conditionals, use conditionals of our own */
355                if (!mismatch) {
356
357                    ap_log_rerror(
358                            APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00695) "Cached response for %s isn't fresh. Adding "
359                            "conditional request headers.", r->uri);
360
361                    /* Remove existing conditionals that might conflict with ours */
362                    apr_table_unset(r->headers_in, "If-Match");
363                    apr_table_unset(r->headers_in, "If-Modified-Since");
364                    apr_table_unset(r->headers_in, "If-None-Match");
365                    apr_table_unset(r->headers_in, "If-Range");
366                    apr_table_unset(r->headers_in, "If-Unmodified-Since");
367
368                    etag = apr_table_get(h->resp_hdrs, "ETag");
369                    lastmod = apr_table_get(h->resp_hdrs, "Last-Modified");
370
371                    if (etag || lastmod) {
372                        /* If we have a cached etag and/or Last-Modified add in
373                         * our own conditionals.
374                         */
375
376                        if (etag) {
377                            apr_table_set(r->headers_in, "If-None-Match", etag);
378                        }
379
380                        if (lastmod) {
381                            apr_table_set(r->headers_in, "If-Modified-Since",
382                                    lastmod);
383                        }
384
385                        /*
386                         * Do not do Range requests with our own conditionals: If
387                         * we get 304 the Range does not matter and otherwise the
388                         * entity changed and we want to have the complete entity
389                         */
390                        apr_table_unset(r->headers_in, "Range");
391
392                    }
393
394                }
395
396                /* ready to revalidate, pretend we were never here */
397                return DECLINED;
398            }
399
400            /* Okay, this response looks okay.  Merge in our stuff and go. */
401            cache_accept_headers(h, r, h->resp_hdrs, r->headers_out, 0);
402
403            cache->handle = h;
404            return OK;
405        }
406        case DECLINED: {
407            /* try again with next cache type */
408            list = list->next;
409            continue;
410        }
411        default: {
412            /* oo-er! an error */
413            return rv;
414        }
415        }
416    }
417
418    /* if Cache-Control: only-if-cached, and not cached, return 504 */
419    if (cache->control_in.only_if_cached) {
420        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00696)
421                "cache: 'only-if-cached' requested and no cached entity, "
422                "returning 504 Gateway Timeout for: %s", r->uri);
423        return HTTP_GATEWAY_TIME_OUT;
424    }
425
426    return DECLINED;
427}
428
429static apr_status_t cache_canonicalise_key(request_rec *r, apr_pool_t* p,
430        const char *uri, apr_uri_t *parsed_uri, const char **key)
431{
432    cache_server_conf *conf;
433    char *port_str, *hn, *lcs;
434    const char *hostname, *scheme;
435    int i;
436    const char *path;
437    char *querystring;
438
439    if (*key) {
440        /*
441         * We have been here before during the processing of this request.
442         */
443        return APR_SUCCESS;
444    }
445
446    /*
447     * Get the module configuration. We need this for the CacheIgnoreQueryString
448     * option below.
449     */
450    conf = (cache_server_conf *) ap_get_module_config(r->server->module_config,
451            &cache_module);
452
453    /*
454     * Use the canonical name to improve cache hit rate, but only if this is
455     * not a proxy request or if this is a reverse proxy request.
456     * We need to handle both cases in the same manner as for the reverse proxy
457     * case we have the following situation:
458     *
459     * If a cached entry is looked up by mod_cache's quick handler r->proxyreq
460     * is still unset in the reverse proxy case as it only gets set in the
461     * translate name hook (either by ProxyPass or mod_rewrite) which is run
462     * after the quick handler hook. This is different to the forward proxy
463     * case where it gets set before the quick handler is run (in the
464     * post_read_request hook).
465     * If a cache entry is created by the CACHE_SAVE filter we always have
466     * r->proxyreq set correctly.
467     * So we must ensure that in the reverse proxy case we use the same code
468     * path and using the canonical name seems to be the right thing to do
469     * in the reverse proxy case.
470     */
471    if (!r->proxyreq || (r->proxyreq == PROXYREQ_REVERSE)) {
472        if (conf->base_uri && conf->base_uri->hostname) {
473            hostname = conf->base_uri->hostname;
474        }
475        else {
476            /* Use _default_ as the hostname if none present, as in mod_vhost */
477            hostname = ap_get_server_name(r);
478            if (!hostname) {
479                hostname = "_default_";
480            }
481        }
482    }
483    else if (parsed_uri->hostname) {
484        /* Copy the parsed uri hostname */
485        hn = apr_pstrdup(p, parsed_uri->hostname);
486        ap_str_tolower(hn);
487        /* const work-around */
488        hostname = hn;
489    }
490    else {
491        /* We are a proxied request, with no hostname. Unlikely
492         * to get very far - but just in case */
493        hostname = "_default_";
494    }
495
496    /*
497     * Copy the scheme, ensuring that it is lower case. If the parsed uri
498     * contains no string or if this is not a proxy request get the http
499     * scheme for this request. As r->parsed_uri.scheme is not set if this
500     * is a reverse proxy request, it is ensured that the cases
501     * "no proxy request" and "reverse proxy request" are handled in the same
502     * manner (see above why this is needed).
503     */
504    if (r->proxyreq && parsed_uri->scheme) {
505        /* Copy the scheme and lower-case it */
506        lcs = apr_pstrdup(p, parsed_uri->scheme);
507        ap_str_tolower(lcs);
508        /* const work-around */
509        scheme = lcs;
510    }
511    else {
512        if (conf->base_uri && conf->base_uri->scheme) {
513            scheme = conf->base_uri->scheme;
514        }
515        else {
516            scheme = ap_http_scheme(r);
517        }
518    }
519
520    /*
521     * If this is a proxy request, but not a reverse proxy request (see comment
522     * above why these cases must be handled in the same manner), copy the
523     * URI's port-string (which may be a service name). If the URI contains
524     * no port-string, use apr-util's notion of the default port for that
525     * scheme - if available. Otherwise use the port-number of the current
526     * server.
527     */
528    if (r->proxyreq && (r->proxyreq != PROXYREQ_REVERSE)) {
529        if (parsed_uri->port_str) {
530            port_str = apr_pcalloc(p, strlen(parsed_uri->port_str) + 2);
531            port_str[0] = ':';
532            for (i = 0; parsed_uri->port_str[i]; i++) {
533                port_str[i + 1] = apr_tolower(parsed_uri->port_str[i]);
534            }
535        }
536        else if (apr_uri_port_of_scheme(scheme)) {
537            port_str = apr_psprintf(p, ":%u", apr_uri_port_of_scheme(scheme));
538        }
539        else {
540            /* No port string given in the AbsoluteUri, and we have no
541             * idea what the default port for the scheme is. Leave it
542             * blank and live with the inefficiency of some extra cached
543             * entities.
544             */
545            port_str = "";
546        }
547    }
548    else {
549        if (conf->base_uri && conf->base_uri->port_str) {
550            port_str = conf->base_uri->port_str;
551        }
552        else if (conf->base_uri && conf->base_uri->hostname) {
553            port_str = "";
554        }
555        else {
556            /* Use the server port */
557            port_str = apr_psprintf(p, ":%u", ap_get_server_port(r));
558        }
559    }
560
561    /*
562     * Check if we need to ignore session identifiers in the URL and do so
563     * if needed.
564     */
565    path = uri;
566    querystring = parsed_uri->query;
567    if (conf->ignore_session_id->nelts) {
568        int i;
569        char **identifier;
570
571        identifier = (char **) conf->ignore_session_id->elts;
572        for (i = 0; i < conf->ignore_session_id->nelts; i++, identifier++) {
573            int len;
574            const char *param;
575
576            len = strlen(*identifier);
577            /*
578             * Check that we have a parameter separator in the last segment
579             * of the path and that the parameter matches our identifier
580             */
581            if ((param = ap_strrchr_c(path, ';'))
582                    && !strncmp(param + 1, *identifier, len)
583                    && (*(param + len + 1) == '=')
584                    && !ap_strchr_c(param + len + 2, '/')) {
585                path = apr_pstrmemdup(p, path, param - path);
586                continue;
587            }
588            /*
589             * Check if the identifier is in the querystring and cut it out.
590             */
591            if (querystring) {
592                /*
593                 * First check if the identifier is at the beginning of the
594                 * querystring and followed by a '='
595                 */
596                if (!strncmp(querystring, *identifier, len)
597                        && (*(querystring + len) == '=')) {
598                    param = querystring;
599                }
600                else {
601                    char *complete;
602
603                    /*
604                     * In order to avoid subkey matching (PR 48401) prepend
605                     * identifier with a '&' and append a '='
606                     */
607                    complete = apr_pstrcat(p, "&", *identifier, "=", NULL);
608                    param = strstr(querystring, complete);
609                    /* If we found something we are sitting on the '&' */
610                    if (param) {
611                        param++;
612                    }
613                }
614                if (param) {
615                    const char *amp;
616
617                    if (querystring != param) {
618                        querystring = apr_pstrndup(p, querystring,
619                                param - querystring);
620                    }
621                    else {
622                        querystring = "";
623                    }
624
625                    if ((amp = ap_strchr_c(param + len + 1, '&'))) {
626                        querystring = apr_pstrcat(p, querystring, amp + 1,
627                                NULL);
628                    }
629                    else {
630                        /*
631                         * If querystring is not "", then we have the case
632                         * that the identifier parameter we removed was the
633                         * last one in the original querystring. Hence we have
634                         * a trailing '&' which needs to be removed.
635                         */
636                        if (*querystring) {
637                            querystring[strlen(querystring) - 1] = '\0';
638                        }
639                    }
640                }
641            }
642        }
643    }
644
645    /* Key format is a URI, optionally without the query-string */
646    if (conf->ignorequerystring) {
647        *key = apr_pstrcat(p, scheme, "://", hostname, port_str, path, "?",
648                NULL);
649    }
650    else {
651        *key = apr_pstrcat(p, scheme, "://", hostname, port_str, path, "?",
652                querystring, NULL);
653    }
654
655    /*
656     * Store the key in the request_config for the cache as r->parsed_uri
657     * might have changed in the time from our first visit here triggered by the
658     * quick handler and our possible second visit triggered by the CACHE_SAVE
659     * filter (e.g. r->parsed_uri got unescaped). In this case we would save the
660     * resource in the cache under a key where it is never found by the quick
661     * handler during following requests.
662     */
663    ap_log_rerror(
664            APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00698) "cache: Key for entity %s?%s is %s", uri, parsed_uri->query, *key);
665
666    return APR_SUCCESS;
667}
668
669apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
670        const char **key)
671{
672    return cache_canonicalise_key(r, p, r->uri, &r->parsed_uri, key);
673}
674
675/*
676 * Invalidate a specific URL entity in all caches
677 *
678 * All cached entities for this URL are removed, usually in
679 * response to a POST/PUT or DELETE.
680 *
681 * This function returns OK if at least one entity was found and
682 * removed, and DECLINED if no cached entities were removed.
683 */
684int cache_invalidate(cache_request_rec *cache, request_rec *r)
685{
686    cache_provider_list *list;
687    apr_status_t rv, status = DECLINED;
688    cache_handle_t *h;
689    apr_uri_t location_uri;
690    apr_uri_t content_location_uri;
691
692    const char *location, *location_key = NULL;
693    const char *content_location, *content_location_key = NULL;
694
695    if (!cache) {
696        /* This should never happen */
697        ap_log_rerror(
698                APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00697) "cache: No cache request information available for key"
699                " generation");
700        return DECLINED;
701    }
702
703    if (!cache->key) {
704        rv = cache_generate_key(r, r->pool, &cache->key);
705        if (rv != APR_SUCCESS) {
706            return DECLINED;
707        }
708    }
709
710    location = apr_table_get(r->headers_out, "Location");
711    if (location) {
712        if (APR_SUCCESS != apr_uri_parse(r->pool, location, &location_uri)
713                || APR_SUCCESS
714                        != cache_canonicalise_key(r, r->pool, location,
715                                &location_uri, &location_key)
716                || !(r->parsed_uri.hostname && location_uri.hostname
717                        && !strcmp(r->parsed_uri.hostname,
718                                location_uri.hostname))) {
719            location_key = NULL;
720        }
721    }
722
723    content_location = apr_table_get(r->headers_out, "Content-Location");
724    if (content_location) {
725        if (APR_SUCCESS
726                != apr_uri_parse(r->pool, content_location,
727                        &content_location_uri)
728                || APR_SUCCESS
729                        != cache_canonicalise_key(r, r->pool, content_location,
730                                &content_location_uri, &content_location_key)
731                || !(r->parsed_uri.hostname && content_location_uri.hostname
732                        && !strcmp(r->parsed_uri.hostname,
733                                content_location_uri.hostname))) {
734            content_location_key = NULL;
735        }
736    }
737
738    /* go through the cache types */
739    h = apr_palloc(r->pool, sizeof(cache_handle_t));
740
741    list = cache->providers;
742
743    while (list) {
744
745        /* invalidate the request uri */
746        rv = list->provider->open_entity(h, r, cache->key);
747        if (OK == rv) {
748            rv = list->provider->invalidate_entity(h, r);
749            status = OK;
750        }
751        ap_log_rerror(
752                APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02468) "cache: Attempted to invalidate cached entity with key: %s", cache->key);
753
754        /* invalidate the Location */
755        if (location_key) {
756            rv = list->provider->open_entity(h, r, location_key);
757            if (OK == rv) {
758                rv = list->provider->invalidate_entity(h, r);
759                status = OK;
760            }
761            ap_log_rerror(
762                    APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02469) "cache: Attempted to invalidate cached entity with key: %s", location_key);
763        }
764
765        /* invalidate the Content-Location */
766        if (content_location_key) {
767            rv = list->provider->open_entity(h, r, content_location_key);
768            if (OK == rv) {
769                rv = list->provider->invalidate_entity(h, r);
770                status = OK;
771            }
772            ap_log_rerror(
773                    APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02470) "cache: Attempted to invalidate cached entity with key: %s", content_location_key);
774        }
775
776        list = list->next;
777    }
778
779    return status;
780}
781