1 /** @file
2 
3   A brief file description
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 /*****************************************************************************
25  *
26  *  CacheControl.cc - Implementation to Cache Control system
27  *
28  *
29  ****************************************************************************/
30 
31 #include <sys/types.h>
32 
33 #include "tscore/ink_config.h"
34 #include "tscore/Filenames.h"
35 #include "CacheControl.h"
36 #include "ControlMatcher.h"
37 #include "Main.h"
38 #include "P_EventSystem.h"
39 #include "ProxyConfig.h"
40 #include "HTTP.h"
41 #include "HttpConfig.h"
42 #include "P_Cache.h"
43 #include "tscore/Regex.h"
44 
45 static const char modulePrefix[] = "[CacheControl]";
46 
47 #define TWEAK_CACHE_RESPONSES_TO_COOKIES "cache-responses-to-cookies"
48 
49 static const char *CC_directive_str[CC_NUM_TYPES] = {
50   "INVALID",
51   "REVALIDATE_AFTER",
52   "NEVER_CACHE",
53   "STANDARD_CACHE",
54   "IGNORE_NO_CACHE",
55   "IGNORE_CLIENT_NO_CACHE",
56   "IGNORE_SERVER_NO_CACHE",
57   "PIN_IN_CACHE",
58   "TTL_IN_CACHE"
59   // "CACHE_AUTH_CONTENT"
60 };
61 
62 using CC_table = ControlMatcher<CacheControlRecord, CacheControlResult>;
63 
64 // Global Ptrs
65 static Ptr<ProxyMutex> reconfig_mutex;
66 CC_table *CacheControlTable = nullptr;
67 
68 // struct CC_FreerContinuation
69 // Continuation to free old cache control lists after
70 //  a timeout
71 //
72 struct CC_FreerContinuation;
73 using CC_FreerContHandler = int (CC_FreerContinuation::*)(int, void *);
74 struct CC_FreerContinuation : public Continuation {
75   CC_table *p;
76   int
freeEventCC_FreerContinuation77   freeEvent(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
78   {
79     Debug("cache_control", "Deleting old table");
80     delete p;
81     delete this;
82     return EVENT_DONE;
83   }
CC_FreerContinuationCC_FreerContinuation84   CC_FreerContinuation(CC_table *ap) : Continuation(nullptr), p(ap)
85   {
86     SET_HANDLER((CC_FreerContHandler)&CC_FreerContinuation::freeEvent);
87   }
88 };
89 
90 // struct CC_UpdateContinuation
91 //
92 //   Used to read the cache.conf file after the manager signals
93 //      a change
94 //
95 struct CC_UpdateContinuation : public Continuation {
96   int
file_update_handlerCC_UpdateContinuation97   file_update_handler(int /* etype ATS_UNUSED */, void * /* data ATS_UNUSED */)
98   {
99     reloadCacheControl();
100     delete this;
101     return EVENT_DONE;
102   }
CC_UpdateContinuationCC_UpdateContinuation103   CC_UpdateContinuation(Ptr<ProxyMutex> &m) : Continuation(m) { SET_HANDLER(&CC_UpdateContinuation::file_update_handler); }
104 };
105 
106 int
cacheControlFile_CB(const char *,RecDataT,RecData,void *)107 cacheControlFile_CB(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */, RecData /* data ATS_UNUSED */,
108                     void * /* cookie ATS_UNUSED */)
109 {
110   eventProcessor.schedule_imm(new CC_UpdateContinuation(reconfig_mutex), ET_CACHE);
111   return 0;
112 }
113 
114 //
115 //   Begin API functions
116 //
117 bool
host_rule_in_CacheControlTable()118 host_rule_in_CacheControlTable()
119 {
120   return (CacheControlTable->hostMatch ? true : false);
121 }
122 
123 bool
ip_rule_in_CacheControlTable()124 ip_rule_in_CacheControlTable()
125 {
126   return (CacheControlTable->ipMatch ? true : false);
127 }
128 
129 void
initCacheControl()130 initCacheControl()
131 {
132   ink_assert(CacheControlTable == nullptr);
133   reconfig_mutex    = new_ProxyMutex();
134   CacheControlTable = new CC_table("proxy.config.cache.control.filename", modulePrefix, &http_dest_tags);
135   REC_RegisterConfigUpdateFunc("proxy.config.cache.control.filename", cacheControlFile_CB, nullptr);
136 }
137 
138 // void reloadCacheControl()
139 //
140 //  Called when the cache.conf file changes.  Since it called
141 //   infrequently, we do the load of new file as blocking I/O and
142 //   lock acquire is also blocking
143 //
144 void
reloadCacheControl()145 reloadCacheControl()
146 {
147   Note("%s loading ...", ts::filename::CACHE);
148 
149   CC_table *newTable;
150 
151   Debug("cache_control", "%s updated, reloading", ts::filename::CACHE);
152   eventProcessor.schedule_in(new CC_FreerContinuation(CacheControlTable), CACHE_CONTROL_TIMEOUT, ET_CACHE);
153   newTable = new CC_table("proxy.config.cache.control.filename", modulePrefix, &http_dest_tags);
154   ink_atomic_swap(&CacheControlTable, newTable);
155 
156   Note("%s finished loading", ts::filename::CACHE);
157 }
158 
159 void
getCacheControl(CacheControlResult * result,HttpRequestData * rdata,const OverridableHttpConfigParams * h_txn_conf,char * tag)160 getCacheControl(CacheControlResult *result, HttpRequestData *rdata, const OverridableHttpConfigParams *h_txn_conf, char *tag)
161 {
162   rdata->tag = tag;
163   CacheControlTable->Match(rdata, result);
164 
165   if (h_txn_conf->cache_ignore_client_no_cache) {
166     result->ignore_client_no_cache = true;
167   }
168 
169   if (h_txn_conf->cache_ignore_server_no_cache) {
170     result->ignore_server_no_cache = true;
171   }
172 
173   if (!h_txn_conf->cache_ignore_client_cc_max_age) {
174     result->ignore_client_cc_max_age = false;
175   }
176 }
177 
178 //
179 //   End API functions
180 //
181 
182 // void CacheControlResult::Print()
183 //
184 //  Debugging Method
185 //
186 void
Print() const187 CacheControlResult::Print() const
188 {
189   printf("\t reval: %d, never-cache: %d, pin: %d, ignore-c: %d ignore-s: %d\n", revalidate_after, never_cache, pin_in_cache_for,
190          ignore_client_no_cache, ignore_server_no_cache);
191 }
192 
193 // void CacheControlRecord::Print()
194 //
195 //  Debugging Method
196 //
197 void
Print() const198 CacheControlRecord::Print() const
199 {
200   switch (this->directive) {
201   case CC_REVALIDATE_AFTER:
202     printf("\t\tDirective: %s : %d\n", CC_directive_str[CC_REVALIDATE_AFTER], this->time_arg);
203     break;
204   case CC_PIN_IN_CACHE:
205     printf("\t\tDirective: %s : %d\n", CC_directive_str[CC_PIN_IN_CACHE], this->time_arg);
206     break;
207   case CC_TTL_IN_CACHE:
208     printf("\t\tDirective: %s : %d\n", CC_directive_str[CC_TTL_IN_CACHE], this->time_arg);
209     break;
210   case CC_IGNORE_CLIENT_NO_CACHE:
211   case CC_IGNORE_SERVER_NO_CACHE:
212   case CC_NEVER_CACHE:
213   case CC_STANDARD_CACHE:
214   case CC_IGNORE_NO_CACHE:
215     printf("\t\tDirective: %s\n", CC_directive_str[this->directive]);
216     break;
217   case CC_INVALID:
218   case CC_NUM_TYPES:
219     printf("\t\tDirective: INVALID\n");
220     break;
221   }
222   if (cache_responses_to_cookies >= 0) {
223     printf("\t\t  - " TWEAK_CACHE_RESPONSES_TO_COOKIES ":%d\n", cache_responses_to_cookies);
224   }
225   ControlBase::Print();
226 }
227 
228 // Result CacheControlRecord::Init(matcher_line* line_info)
229 //
230 //    matcher_line* line_info - contains parsed label/value
231 //      pairs of the current cache.config line
232 //
233 //    Returns NULL if everything is OK
234 //      Otherwise, returns an error string that the caller MUST
235 //        DEALLOCATE with free()
236 //
237 Result
Init(matcher_line * line_info)238 CacheControlRecord::Init(matcher_line *line_info)
239 {
240   int time_in;
241   const char *tmp;
242   char *label;
243   char *val;
244   bool d_found = false;
245 
246   this->line_num = line_info->line_num;
247 
248   // First pass for optional tweaks.
249   for (int i = 0; i < MATCHER_MAX_TOKENS && line_info->num_el; ++i) {
250     bool used = false;
251     label     = line_info->line[0][i];
252     val       = line_info->line[1][i];
253     if (!label) {
254       continue;
255     }
256 
257     if (strcasecmp(label, TWEAK_CACHE_RESPONSES_TO_COOKIES) == 0) {
258       char *ptr = nullptr;
259       int v     = strtol(val, &ptr, 0);
260       if (!ptr || v < 0 || v > 4) {
261         return Result::failure("Value for " TWEAK_CACHE_RESPONSES_TO_COOKIES " must be an integer in the range 0..4");
262       } else {
263         cache_responses_to_cookies = v;
264       }
265       used = true;
266     }
267 
268     // Clip pair if used.
269     if (used) {
270       line_info->line[0][i] = nullptr;
271       --(line_info->num_el);
272     }
273   }
274 
275   // Now look for the directive.
276   for (int i = 0; i < MATCHER_MAX_TOKENS; i++) {
277     label = line_info->line[0][i];
278     val   = line_info->line[1][i];
279 
280     if (label == nullptr) {
281       continue;
282     }
283 
284     if (strcasecmp(label, "action") == 0) {
285       if (strcasecmp(val, "never-cache") == 0) {
286         directive = CC_NEVER_CACHE;
287         d_found   = true;
288       } else if (strcasecmp(val, "standard-cache") == 0) {
289         directive = CC_STANDARD_CACHE;
290         d_found   = true;
291       } else if (strcasecmp(val, "ignore-no-cache") == 0) {
292         directive = CC_IGNORE_NO_CACHE;
293         d_found   = true;
294       } else if (strcasecmp(val, "ignore-client-no-cache") == 0) {
295         directive = CC_IGNORE_CLIENT_NO_CACHE;
296         d_found   = true;
297       } else if (strcasecmp(val, "ignore-server-no-cache") == 0) {
298         directive = CC_IGNORE_SERVER_NO_CACHE;
299         d_found   = true;
300       } else {
301         return Result::failure("%s Invalid action at line %d in %s", modulePrefix, line_num, ts::filename::CACHE);
302       }
303     } else {
304       if (strcasecmp(label, "revalidate") == 0) {
305         directive = CC_REVALIDATE_AFTER;
306         d_found   = true;
307       } else if (strcasecmp(label, "pin-in-cache") == 0) {
308         directive = CC_PIN_IN_CACHE;
309         d_found   = true;
310       } else if (strcasecmp(label, "ttl-in-cache") == 0) {
311         directive = CC_TTL_IN_CACHE;
312         d_found   = true;
313       }
314       // Process the time argument for the remaining directives
315       if (d_found == true) {
316         tmp = processDurationString(val, &time_in);
317         if (tmp == nullptr) {
318           this->time_arg = time_in;
319 
320         } else {
321           return Result::failure("%s %s at line %d in %s", modulePrefix, tmp, line_num, ts::filename::CACHE);
322         }
323       }
324     }
325 
326     if (d_found == true) {
327       // Consume the label/value pair we used
328       line_info->line[0][i] = nullptr;
329       line_info->num_el--;
330       break;
331     }
332   }
333 
334   if (d_found == false) {
335     return Result::failure("%s No directive in %s at line %d", modulePrefix, ts::filename::CACHE, line_num);
336   }
337   // Process any modifiers to the directive, if they exist
338   if (line_info->num_el > 0) {
339     tmp = ProcessModifiers(line_info);
340 
341     if (tmp != nullptr) {
342       return Result::failure("%s %s at line %d in %s", modulePrefix, tmp, line_num, ts::filename::CACHE);
343     }
344   }
345 
346   return Result::ok();
347 }
348 
349 // void CacheControlRecord::UpdateMatch(CacheControlResult* result, RequestData* rdata)
350 //
351 //    Updates the parameters in result if the this element
352 //     appears later in the file
353 //
354 void
UpdateMatch(CacheControlResult * result,RequestData * rdata)355 CacheControlRecord::UpdateMatch(CacheControlResult *result, RequestData *rdata)
356 {
357   bool match               = false;
358   HttpRequestData *h_rdata = (HttpRequestData *)rdata;
359 
360   switch (this->directive) {
361   case CC_REVALIDATE_AFTER:
362     if (this->CheckForMatch(h_rdata, result->reval_line) == true) {
363       result->revalidate_after = time_arg;
364       result->reval_line       = this->line_num;
365       match                    = true;
366     }
367     break;
368   case CC_NEVER_CACHE:
369     if (this->CheckForMatch(h_rdata, result->never_line) == true) {
370       // ttl-in-cache overrides never-cache
371       if (result->ttl_line == -1) {
372         result->never_cache = true;
373         result->never_line  = this->line_num;
374         match               = true;
375       }
376     }
377     break;
378   case CC_STANDARD_CACHE:
379     // Standard cache just overrides never-cache
380     if (this->CheckForMatch(h_rdata, result->never_line) == true) {
381       result->never_cache = false;
382       result->never_line  = this->line_num;
383       match               = true;
384     }
385     break;
386   case CC_IGNORE_NO_CACHE:
387   // We cover both client & server cases for this directive
388   //  FALLTHROUGH
389   case CC_IGNORE_CLIENT_NO_CACHE:
390     if (this->CheckForMatch(h_rdata, result->ignore_client_line) == true) {
391       result->ignore_client_no_cache = true;
392       result->ignore_client_line     = this->line_num;
393       match                          = true;
394     }
395     if (this->directive != CC_IGNORE_NO_CACHE) {
396       break;
397     }
398   // FALLTHROUGH
399   case CC_IGNORE_SERVER_NO_CACHE:
400     if (this->CheckForMatch(h_rdata, result->ignore_server_line) == true) {
401       result->ignore_server_no_cache = true;
402       result->ignore_server_line     = this->line_num;
403       match                          = true;
404     }
405     break;
406   case CC_PIN_IN_CACHE:
407     if (this->CheckForMatch(h_rdata, result->pin_line) == true) {
408       result->pin_in_cache_for = time_arg;
409       result->pin_line         = this->line_num;
410       match                    = true;
411     }
412     break;
413   case CC_TTL_IN_CACHE:
414     if (this->CheckForMatch(h_rdata, result->ttl_line) == true) {
415       result->ttl_in_cache = time_arg;
416       result->ttl_line     = this->line_num;
417       // ttl-in-cache overrides never-cache
418       result->never_cache = false;
419       result->never_line  = this->line_num;
420       match               = true;
421     }
422     break;
423   case CC_INVALID:
424   case CC_NUM_TYPES:
425   default:
426     // Should not get here
427     Warning("Impossible directive in CacheControlRecord::UpdateMatch");
428     ink_assert(0);
429     break;
430   }
431 
432   if (cache_responses_to_cookies >= 0) {
433     result->cache_responses_to_cookies = cache_responses_to_cookies;
434   }
435 
436   if (match == true) {
437     char crtc_debug[80];
438     if (result->cache_responses_to_cookies >= 0) {
439       snprintf(crtc_debug, sizeof(crtc_debug), " [" TWEAK_CACHE_RESPONSES_TO_COOKIES "=%d]", result->cache_responses_to_cookies);
440     } else {
441       crtc_debug[0] = 0;
442     }
443 
444     Debug("cache_control", "Matched with for %s at line %d%s", CC_directive_str[this->directive], this->line_num, crtc_debug);
445   }
446 }
447