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  * cache_scan.cc:  use TSCacheScan to print URLs and headers for objects in
26  *                 the cache when endpoint /show-cache is requested
27  */
28 #include <cstdio>
29 #include <cstring>
30 #include <climits>
31 #include <cstdlib>
32 
33 #include "ts/ts.h"
34 #include "ts/experimental.h"
35 #include "tscore/ink_defs.h"
36 
37 #define PLUGIN_NAME "cache_scan"
38 
39 static TSCont global_contp;
40 
41 struct cache_scan_state_t {
42   TSVConn net_vc;
43   TSVConn cache_vc;
44   TSVIO read_vio;
45   TSVIO write_vio;
46 
47   TSIOBuffer req_buffer;
48   TSIOBuffer resp_buffer;
49   TSIOBufferReader resp_reader;
50 
51   TSHttpTxn http_txnp;
52   TSAction pending_action;
53   TSCacheKey key_to_delete;
54 
55   int64_t total_bytes;
56   int total_items;
57   int done;
58 
59   bool write_pending;
60 };
61 
62 using cache_scan_state = struct cache_scan_state_t;
63 
64 //----------------------------------------------------------------------------
65 static int
handle_scan(TSCont contp,TSEvent event,void * edata)66 handle_scan(TSCont contp, TSEvent event, void *edata)
67 {
68   cache_scan_state *cstate = static_cast<cache_scan_state *>(TSContDataGet(contp));
69 
70   if (event == TS_EVENT_CACHE_REMOVE) {
71     cstate->done       = 1;
72     const char error[] = "Cache remove operation succeeded";
73     cstate->cache_vc   = static_cast<TSVConn>(edata);
74     cstate->write_vio  = TSVConnWrite(cstate->net_vc, contp, cstate->resp_reader, INT64_MAX);
75     cstate->total_bytes += TSIOBufferWrite(cstate->resp_buffer, error, sizeof(error) - 1);
76     TSVIONBytesSet(cstate->write_vio, cstate->total_bytes);
77     TSVIOReenable(cstate->write_vio);
78     return 0;
79   }
80 
81   if (event == TS_EVENT_CACHE_REMOVE_FAILED) {
82     cstate->done       = 1;
83     const char error[] = "Cache remove operation failed error=";
84     char rc[12];
85     snprintf(rc, 12, "%p", edata);
86     cstate->cache_vc  = static_cast<TSVConn>(edata);
87     cstate->write_vio = TSVConnWrite(cstate->net_vc, contp, cstate->resp_reader, INT64_MAX);
88     cstate->total_bytes += TSIOBufferWrite(cstate->resp_buffer, error, sizeof(error) - 1);
89     cstate->total_bytes += TSIOBufferWrite(cstate->resp_buffer, rc, strlen(rc));
90 
91     TSVIONBytesSet(cstate->write_vio, cstate->total_bytes);
92     TSVIOReenable(cstate->write_vio);
93     return 0;
94   }
95 
96   // first scan event, save vc and start write
97   if (event == TS_EVENT_CACHE_SCAN) {
98     cstate->cache_vc  = static_cast<TSVConn>(edata);
99     cstate->write_vio = TSVConnWrite(cstate->net_vc, contp, cstate->resp_reader, INT64_MAX);
100     return TS_EVENT_CONTINUE;
101   }
102   // just stop scanning if blocked or failed
103   if (event == TS_EVENT_CACHE_SCAN_FAILED || event == TS_EVENT_CACHE_SCAN_OPERATION_BLOCKED ||
104       event == TS_EVENT_CACHE_SCAN_OPERATION_FAILED) {
105     cstate->done = 1;
106     if (cstate->resp_buffer) {
107       const char error[] = "Cache scan operation blocked or failed";
108       cstate->total_bytes += TSIOBufferWrite(cstate->resp_buffer, error, sizeof(error) - 1);
109     }
110     if (cstate->write_vio) {
111       TSVIONBytesSet(cstate->write_vio, cstate->total_bytes);
112       TSVIOReenable(cstate->write_vio);
113     }
114     return TS_CACHE_SCAN_RESULT_DONE;
115   }
116 
117   // grab header and print url to outgoing vio
118   if (event == TS_EVENT_CACHE_SCAN_OBJECT) {
119     if (cstate->done) {
120       return TS_CACHE_SCAN_RESULT_DONE;
121     }
122     TSCacheHttpInfo cache_infop = static_cast<TSCacheHttpInfo>(edata);
123 
124     TSMBuffer req_bufp, resp_bufp;
125     TSMLoc req_hdr_loc, resp_hdr_loc;
126     TSMLoc url_loc;
127 
128     int url_len;
129     const char s1[] = "URL: ", s2[] = "\n";
130     cstate->total_bytes += TSIOBufferWrite(cstate->resp_buffer, s1, sizeof(s1) - 1);
131     TSCacheHttpInfoReqGet(cache_infop, &req_bufp, &req_hdr_loc);
132     if (TS_SUCCESS == TSHttpHdrUrlGet(req_bufp, req_hdr_loc, &url_loc)) {
133       char *url = TSUrlStringGet(req_bufp, url_loc, &url_len);
134 
135       cstate->total_bytes += TSIOBufferWrite(cstate->resp_buffer, url, url_len);
136       cstate->total_bytes += TSIOBufferWrite(cstate->resp_buffer, s2, sizeof(s2) - 1);
137 
138       TSfree(url);
139       TSHandleMLocRelease(req_bufp, req_hdr_loc, url_loc);
140       TSHandleMLocRelease(req_bufp, TS_NULL_MLOC, req_hdr_loc);
141     }
142 
143     // print the response headers
144     TSCacheHttpInfoRespGet(cache_infop, &resp_bufp, &resp_hdr_loc);
145     cstate->total_bytes += TSMimeHdrLengthGet(resp_bufp, resp_hdr_loc);
146     TSMimeHdrPrint(resp_bufp, resp_hdr_loc, cstate->resp_buffer);
147     TSHandleMLocRelease(resp_bufp, TS_NULL_MLOC, resp_hdr_loc);
148 
149     cstate->total_bytes += TSIOBufferWrite(cstate->resp_buffer, s2, sizeof(s2) - 1);
150     if (!cstate->write_pending) {
151       cstate->write_pending = true;
152       TSVIOReenable(cstate->write_vio);
153     }
154 
155     cstate->total_items++;
156     return TS_CACHE_SCAN_RESULT_CONTINUE;
157   }
158   // CACHE_SCAN_DONE: ready to close the vc on the next write reenable
159   if (event == TS_EVENT_CACHE_SCAN_DONE) {
160     cstate->done = 1;
161     char s[512];
162     int s_len = snprintf(s, sizeof(s),
163                          "</pre></p>\n<p>%d total objects in cache</p>\n"
164                          "<form method=\"GET\" action=\"/show-cache\">"
165                          "Enter URL to delete: <input type=\"text\" size=\"40\" name=\"remove_url\">"
166                          "<input type=\"submit\"  value=\"Delete URL\">",
167                          cstate->total_items);
168     cstate->total_bytes += TSIOBufferWrite(cstate->resp_buffer, s, s_len);
169     TSVIONBytesSet(cstate->write_vio, cstate->total_bytes);
170     if (!cstate->write_pending) {
171       cstate->write_pending = true;
172       TSVIOReenable(cstate->write_vio);
173     }
174     return TS_CACHE_SCAN_RESULT_DONE;
175   }
176 
177   TSError("[%s] Unknown event in handle_scan: %d", PLUGIN_NAME, event);
178   return -1;
179 }
180 
181 //----------------------------------------------------------------------------
182 static int
handle_accept(TSCont contp,TSEvent event,TSVConn vc)183 handle_accept(TSCont contp, TSEvent event, TSVConn vc)
184 {
185   cache_scan_state *cstate = static_cast<cache_scan_state *>(TSContDataGet(contp));
186 
187   if (event == TS_EVENT_NET_ACCEPT) {
188     if (cstate) {
189       // setup vc, buffers
190       cstate->net_vc = vc;
191 
192       cstate->req_buffer  = TSIOBufferCreate();
193       cstate->resp_buffer = TSIOBufferCreate();
194       cstate->resp_reader = TSIOBufferReaderAlloc(cstate->resp_buffer);
195 
196       cstate->read_vio = TSVConnRead(cstate->net_vc, contp, cstate->req_buffer, INT64_MAX);
197     } else {
198       TSVConnClose(vc);
199       TSContDestroy(contp);
200     }
201   } else {
202     // net_accept failed
203     if (cstate) {
204       TSfree(cstate);
205     }
206     TSContDestroy(contp);
207   }
208 
209   return 0;
210 }
211 
212 //----------------------------------------------------------------------------
213 static void
cleanup(TSCont contp)214 cleanup(TSCont contp)
215 {
216   // shutdown vc and free memory
217   cache_scan_state *cstate = static_cast<cache_scan_state *>(TSContDataGet(contp));
218 
219   if (cstate) {
220     // cancel any pending cache scan actions, since we will be destroying the
221     // continuation
222     if (cstate->pending_action) {
223       TSActionCancel(cstate->pending_action);
224     }
225 
226     if (cstate->net_vc) {
227       TSVConnShutdown(cstate->net_vc, 1, 1);
228     }
229 
230     if (cstate->req_buffer) {
231       TSIOBufferDestroy(cstate->req_buffer);
232       cstate->req_buffer = nullptr;
233     }
234 
235     if (cstate->key_to_delete) {
236       if (TSCacheKeyDestroy(cstate->key_to_delete) == TS_ERROR) {
237         TSError("[%s] Failed to destroy cache key", PLUGIN_NAME);
238       }
239       cstate->key_to_delete = nullptr;
240     }
241 
242     if (cstate->resp_buffer) {
243       TSIOBufferDestroy(cstate->resp_buffer);
244       cstate->resp_buffer = nullptr;
245     }
246 
247     TSVConnClose(cstate->net_vc);
248     TSfree(cstate);
249   }
250   TSContDestroy(contp);
251 }
252 
253 //----------------------------------------------------------------------------
254 static int
handle_io(TSCont contp,TSEvent event,void *)255 handle_io(TSCont contp, TSEvent event, void * /* edata ATS_UNUSED */)
256 {
257   cache_scan_state *cstate = static_cast<cache_scan_state *>(TSContDataGet(contp));
258 
259   switch (event) {
260   case TS_EVENT_VCONN_READ_READY:
261   case TS_EVENT_VCONN_READ_COMPLETE: {
262     // we don't care about the request, so just shut down the read vc
263     TSVConnShutdown(cstate->net_vc, 1, 0);
264     // setup the response headers so we are ready to write body
265     char hdrs[]         = "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n";
266     cstate->total_bytes = TSIOBufferWrite(cstate->resp_buffer, hdrs, sizeof(hdrs) - 1);
267 
268     if (cstate->key_to_delete) {
269       TSAction actionp = TSCacheRemove(contp, cstate->key_to_delete);
270       if (!TSActionDone(actionp)) {
271         cstate->pending_action = actionp;
272       }
273     } else {
274       char head[] = "<h3>Cache Contents:</h3>\n<p><pre>\n";
275       cstate->total_bytes += TSIOBufferWrite(cstate->resp_buffer, head, sizeof(head) - 1);
276       // start scan
277       TSAction actionp = TSCacheScan(contp, nullptr, 512000);
278       if (!TSActionDone(actionp)) {
279         cstate->pending_action = actionp;
280       }
281     }
282 
283     return 0;
284   } break;
285   case TS_EVENT_VCONN_WRITE_READY: {
286     TSDebug(PLUGIN_NAME, "ndone: %" PRId64 " total_bytes: % " PRId64, TSVIONDoneGet(cstate->write_vio), cstate->total_bytes);
287     cstate->write_pending = false;
288     // the cache scan handler should call vio reenable when there is
289     // available data
290     // TSVIOReenable(cstate->write_vio);
291     return 0;
292   } break;
293   case TS_EVENT_VCONN_WRITE_COMPLETE: {
294     TSDebug(PLUGIN_NAME, "write complete");
295     cstate->done = 1;
296     cleanup(contp);
297   } break;
298   case TS_EVENT_VCONN_EOS:
299   default: {
300     cstate->done = 1;
301     cleanup(contp);
302   } break;
303   }
304 
305   return 0;
306 }
307 
308 //----------------------------------------------------------------------------
309 // handler for VConnection and CacheScan events
310 static int
cache_intercept(TSCont contp,TSEvent event,void * edata)311 cache_intercept(TSCont contp, TSEvent event, void *edata)
312 {
313   TSDebug(PLUGIN_NAME, "cache_intercept event: %d", event);
314 
315   switch (event) {
316   case TS_EVENT_NET_ACCEPT:
317   case TS_EVENT_NET_ACCEPT_FAILED:
318     return handle_accept(contp, event, static_cast<TSVConn>(edata));
319   case TS_EVENT_VCONN_READ_READY:
320   case TS_EVENT_VCONN_READ_COMPLETE:
321   case TS_EVENT_VCONN_WRITE_READY:
322   case TS_EVENT_VCONN_WRITE_COMPLETE:
323   case TS_EVENT_VCONN_EOS:
324     return handle_io(contp, event, edata);
325   case TS_EVENT_CACHE_SCAN:
326   case TS_EVENT_CACHE_SCAN_FAILED:
327   case TS_EVENT_CACHE_SCAN_OBJECT:
328   case TS_EVENT_CACHE_SCAN_OPERATION_BLOCKED:
329   case TS_EVENT_CACHE_SCAN_OPERATION_FAILED:
330   case TS_EVENT_CACHE_SCAN_DONE:
331   case TS_EVENT_CACHE_REMOVE:
332   case TS_EVENT_CACHE_REMOVE_FAILED:
333     return handle_scan(contp, event, edata);
334   case TS_EVENT_ERROR:
335     cleanup(contp);
336     return 0;
337   default:
338     TSError("[%s] Unknown event in cache_intercept: %d", PLUGIN_NAME, event);
339     cleanup(contp);
340     return 0;
341   }
342 }
343 
344 // int unescapifyStr(char* buffer)
345 //
346 //   Unescapifies a URL without a making a copy.
347 //    The passed in string is modified
348 //
349 int
unescapifyStr(char * buffer)350 unescapifyStr(char *buffer)
351 {
352   char *read  = buffer;
353   char *write = buffer;
354   char subStr[3];
355 
356   subStr[2] = '\0';
357   while (*read != '\0') {
358     if (*read == '%' && *(read + 1) != '\0' && *(read + 2) != '\0') {
359       subStr[0] = *(++read);
360       subStr[1] = *(++read);
361       *write    = static_cast<char>(strtol(subStr, (char **)nullptr, 16));
362       read++;
363       write++;
364     } else if (*read == '+') {
365       *write = ' ';
366       write++;
367       read++;
368     } else {
369       *write = *read;
370       write++;
371       read++;
372     }
373   }
374   *write = '\0';
375 
376   return (write - buffer);
377 }
378 
379 //----------------------------------------------------------------------------
380 static int
setup_request(TSCont contp,TSHttpTxn txnp)381 setup_request(TSCont contp, TSHttpTxn txnp)
382 {
383   TSMBuffer bufp;
384   TSMLoc hdr_loc;
385   TSMLoc url_loc;
386   TSCont scan_contp;
387   const char *path, *query;
388   cache_scan_state *cstate;
389   int path_len, query_len;
390 
391   TSAssert(contp == global_contp);
392 
393   if (TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
394     TSError("[%s] Couldn't retrieve client request header", PLUGIN_NAME);
395     TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
396     return TS_SUCCESS;
397   }
398 
399   if (TSHttpHdrUrlGet(bufp, hdr_loc, &url_loc) != TS_SUCCESS) {
400     TSError("[%s] Couldn't retrieve request url", PLUGIN_NAME);
401     TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
402     TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
403     return TS_SUCCESS;
404   }
405 
406   path = TSUrlPathGet(bufp, url_loc, &path_len);
407   if (!path) {
408     TSError("[%s] Couldn't retrieve request path", PLUGIN_NAME);
409     TSHandleMLocRelease(bufp, hdr_loc, url_loc);
410     TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
411     TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
412     return TS_SUCCESS;
413   }
414 
415   query = TSUrlHttpQueryGet(bufp, url_loc, &query_len);
416 
417   if (path_len == 10 && !strncmp(path, "show-cache", 10)) {
418     scan_contp = TSContCreate(cache_intercept, TSMutexCreate());
419     TSHttpTxnIntercept(scan_contp, txnp);
420     cstate = static_cast<cache_scan_state *>(TSmalloc(sizeof(cache_scan_state)));
421     memset(cstate, 0, sizeof(cache_scan_state));
422     cstate->http_txnp = txnp;
423 
424     if (query && query_len > 11) {
425       char querybuf[2048];
426       query_len   = static_cast<unsigned>(query_len) > sizeof(querybuf) - 1 ? sizeof(querybuf) - 1 : query_len;
427       char *start = querybuf, *end = querybuf + query_len;
428       size_t del_url_len;
429       memcpy(querybuf, query, query_len);
430       *end  = '\0';
431       start = strstr(querybuf, "remove_url=");
432       if (start && (start == querybuf || *(start - 1) == '&')) {
433         start += 11;
434         if ((end = strstr(start, "&")) != nullptr) {
435           *end = '\0';
436         }
437         del_url_len = unescapifyStr(start);
438         end         = start + del_url_len;
439 
440         cstate->key_to_delete = TSCacheKeyCreate();
441         TSDebug(PLUGIN_NAME, "deleting url: %s", start);
442 
443         TSMBuffer urlBuf = TSMBufferCreate();
444         TSMLoc urlLoc;
445 
446         if (TS_SUCCESS == TSUrlCreate(urlBuf, &urlLoc)) {
447           if (TSUrlParse(urlBuf, urlLoc, (const char **)&start, end) != TS_PARSE_DONE ||
448               TSCacheKeyDigestFromUrlSet(cstate->key_to_delete, urlLoc) != TS_SUCCESS) {
449             TSError("[%s] CacheKeyDigestFromUrlSet failed", PLUGIN_NAME);
450             TSCacheKeyDestroy(cstate->key_to_delete);
451             TSfree(cstate);
452             TSHandleMLocRelease(urlBuf, TS_NULL_MLOC, urlLoc);
453             TSMBufferDestroy(urlBuf);
454             goto Ldone;
455           }
456           TSHandleMLocRelease(urlBuf, TS_NULL_MLOC, urlLoc);
457         } else {
458           TSError("[%s] TSUrlCreate failed", PLUGIN_NAME);
459         }
460         TSMBufferDestroy(urlBuf);
461       }
462     }
463 
464     TSContDataSet(scan_contp, cstate);
465     TSDebug(PLUGIN_NAME, "setup cache intercept");
466   } else {
467     TSDebug(PLUGIN_NAME, "not a cache iter request");
468   }
469 
470 Ldone:
471   TSHandleMLocRelease(bufp, hdr_loc, url_loc);
472   TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
473   TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
474   return TS_SUCCESS;
475 }
476 
477 //----------------------------------------------------------------------------
478 // handler for http txn events
479 static int
cache_print_plugin(TSCont contp,TSEvent event,void * edata)480 cache_print_plugin(TSCont contp, TSEvent event, void *edata)
481 {
482   switch (event) {
483   case TS_EVENT_HTTP_READ_REQUEST_HDR:
484     return setup_request(contp, static_cast<TSHttpTxn>(edata));
485   default:
486     break;
487   }
488   TSHttpTxnReenable(static_cast<TSHttpTxn>(edata), TS_EVENT_HTTP_CONTINUE);
489   return TS_SUCCESS;
490 }
491 
492 //----------------------------------------------------------------------------
493 void
TSPluginInit(int,const char * [])494 TSPluginInit(int /* argc ATS_UNUSED */, const char * /* argv ATS_UNUSED */[])
495 {
496   TSPluginRegistrationInfo info;
497 
498   info.plugin_name   = PLUGIN_NAME;
499   info.vendor_name   = "Apache Software Foundation";
500   info.support_email = "dev@trafficserver.apache.org";
501 
502   if (TSPluginRegister(&info) == TS_SUCCESS) {
503     global_contp = TSContCreate(cache_print_plugin, TSMutexCreate());
504     TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, global_contp);
505   } else {
506     TSError("[%s] Plugin registration failed", PLUGIN_NAME);
507   }
508 }
509