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