xref: /trafficserver/plugins/esi/esi.cc (revision 284a6a8b)
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 #include "tscore/ink_defs.h"
25 
26 #include <cstdio>
27 #include <cstdlib>
28 #include <climits>
29 #include <cstring>
30 #include <string>
31 #include <list>
32 #include <arpa/inet.h>
33 #include <getopt.h>
34 
35 #include "ts/ts.h"
36 #include "ts/experimental.h"
37 #include "ts/remap.h"
38 
39 #include "lib/Utils.h"
40 #include "lib/gzip.h"
41 #include "EsiGzip.h"
42 #include "EsiGunzip.h"
43 #include "EsiProcessor.h"
44 #include "HttpDataFetcher.h"
45 #include "HandlerManager.h"
46 #include "serverIntercept.h"
47 #include "Stats.h"
48 #include "HttpDataFetcherImpl.h"
49 using std::string;
50 using std::list;
51 using namespace EsiLib;
52 using namespace Stats;
53 
54 struct OptionInfo {
55   bool packed_node_support;
56   bool private_response;
57   bool disable_gzip_output;
58   bool first_byte_flush;
59 };
60 
61 static HandlerManager *gHandlerManager = nullptr;
62 static Utils::HeaderValueList gWhitelistCookies;
63 
64 #define DEBUG_TAG "plugin_esi"
65 #define PROCESSOR_DEBUG_TAG "plugin_esi_processor"
66 #define GZIP_DEBUG_TAG "plugin_esi_gzip"
67 #define GUNZIP_DEBUG_TAG "plugin_esi_gunzip"
68 #define PARSER_DEBUG_TAG "plugin_esi_parser"
69 #define FETCHER_DEBUG_TAG "plugin_esi_fetcher"
70 #define VARS_DEBUG_TAG "plugin_esi_vars"
71 #define HANDLER_MGR_DEBUG_TAG "plugin_esi_handler_mgr"
72 #define EXPR_DEBUG_TAG VARS_DEBUG_TAG
73 
74 #define MIME_FIELD_XESI "X-Esi"
75 #define MIME_FIELD_XESI_LEN 5
76 
77 #define HTTP_VALUE_PRIVATE_EXPIRES "-1"
78 #define HTTP_VALUE_PRIVATE_CC "max-age=0, private"
79 
80 enum DataType {
81   DATA_TYPE_RAW_ESI     = 0,
82   DATA_TYPE_GZIPPED_ESI = 1,
83   DATA_TYPE_PACKED_ESI  = 2,
84 };
85 static const char *DATA_TYPE_NAMES_[] = {"RAW_ESI", "GZIPPED_ESI", "PACKED_ESI"};
86 
87 static const char *HEADER_MASK_PREFIX    = "Mask-";
88 static const int HEADER_MASK_PREFIX_SIZE = 5;
89 
90 struct ContData {
91   enum STATE {
92     READING_ESI_DOC,
93     FETCHING_DATA,
94     PROCESSING_COMPLETE,
95   };
96   STATE curr_state;
97   TSVIO input_vio;
98   TSIOBufferReader input_reader = nullptr;
99   TSVIO output_vio;
100   TSIOBuffer output_buffer;
101   TSIOBufferReader output_reader;
102   Variables *esi_vars;
103   HttpDataFetcherImpl *data_fetcher;
104   EsiProcessor *esi_proc;
105   EsiGzip *esi_gzip;
106   EsiGunzip *esi_gunzip;
107   TSCont contp;
108   TSHttpTxn txnp;
109   const struct OptionInfo *option_info = nullptr;
110   char *request_url;
111   sockaddr const *client_addr;
112   DataType input_type;
113   string packed_node_list;
114   string gzipped_data;
115   char debug_tag[32];
116   bool gzip_output;
117   bool initialized;
118   bool xform_closed;
119   bool intercept_header;
120   bool cache_txn;
121   bool head_only;
122 
123   bool os_response_cacheable;
124   list<string> post_headers;
125 
ContDataContData126   ContData(TSCont contptr, TSHttpTxn tx)
127     : curr_state(READING_ESI_DOC),
128       input_vio(nullptr),
129       output_vio(nullptr),
130       output_buffer(nullptr),
131       output_reader(nullptr),
132       esi_vars(nullptr),
133       data_fetcher(nullptr),
134       esi_proc(nullptr),
135       esi_gzip(nullptr),
136       esi_gunzip(nullptr),
137       contp(contptr),
138       txnp(tx),
139       request_url(nullptr),
140       input_type(DATA_TYPE_RAW_ESI),
141       packed_node_list(""),
142       gzipped_data(""),
143       gzip_output(false),
144       initialized(false),
145       xform_closed(false),
146       intercept_header(false),
147       cache_txn(false),
148       head_only(false),
149       os_response_cacheable(true)
150   {
151     client_addr = TSHttpTxnClientAddrGet(txnp);
152     *debug_tag  = '\0';
153   }
154 
155   void fillPostHeader(TSMBuffer bufp, TSMLoc hdr_loc);
156 
157   void getClientState();
158 
159   void getServerState();
160 
161   void checkXformStatus();
162 
163   bool init();
164 
165   ~ContData();
166 };
167 
168 class TSStatSystem : public StatSystem
169 {
170 public:
171   void
create(int handle)172   create(int handle)
173   {
174     g_stat_indices[handle] = TSStatCreate(Stats::STAT_NAMES[handle], TS_RECORDDATATYPE_INT, TS_STAT_PERSISTENT, TS_STAT_SYNC_COUNT);
175   }
176 
177   void
increment(int handle,int step=1)178   increment(int handle, int step = 1)
179   {
180     TSStatIntIncrement(g_stat_indices[handle], step);
181   }
182 };
183 
184 static const char *
createDebugTag(const char * prefix,TSCont contp,string & dest)185 createDebugTag(const char *prefix, TSCont contp, string &dest)
186 {
187   char buf[1024];
188   snprintf(buf, 1024, "%s_%p", prefix, contp);
189   dest.assign(buf);
190   return dest.c_str();
191 }
192 
193 static bool checkHeaderValue(TSMBuffer bufp, TSMLoc hdr_loc, const char *name, int name_len, const char *exp_value = nullptr,
194                              int exp_value_len = 0, bool prefix = false); // forward decl
195 
196 static bool checkForCacheHeader(const char *name, int name_len, const char *value, int value_len, bool &cacheable);
197 
198 void
checkXformStatus()199 ContData::checkXformStatus()
200 {
201   if (!xform_closed) {
202     int retval = TSVConnClosedGet(contp);
203     if ((retval == TS_ERROR) || retval) {
204       if (retval == TS_ERROR) {
205         TSDebug(debug_tag, "[%s] Error while getting close status of transformation at state %d", __FUNCTION__, curr_state);
206       } else {
207         TSDebug(debug_tag, "[%s] Vconn closed", __FUNCTION__);
208       }
209       xform_closed = true;
210     }
211   }
212 }
213 
214 bool
init()215 ContData::init()
216 {
217   if (initialized) {
218     TSError("[esi][%s] ContData already initialized!", __FUNCTION__);
219     return false;
220   }
221 
222   string tmp_tag;
223   createDebugTag(DEBUG_TAG, contp, tmp_tag);
224   memcpy(debug_tag, tmp_tag.c_str(), tmp_tag.length() + 1);
225 
226   checkXformStatus();
227 
228   bool retval = false;
229 
230   if (!xform_closed) {
231     // Get upstream VIO
232     input_vio = TSVConnWriteVIOGet(contp);
233     if (!input_vio) {
234       TSError("[esi][%s] Error while getting input vio", __FUNCTION__);
235       goto lReturn;
236     }
237     input_reader = TSVIOReaderGet(input_vio);
238 
239     // get downstream VIO
240     TSVConn output_conn;
241     output_conn = TSTransformOutputVConnGet(contp);
242     if (!output_conn) {
243       TSError("[esi][%s] Error while getting transform VC", __FUNCTION__);
244       goto lReturn;
245     }
246     output_buffer = TSIOBufferCreate();
247     output_reader = TSIOBufferReaderAlloc(output_buffer);
248 
249     // we don't know how much data we are going to write, so INT_MAX
250     output_vio = TSVConnWrite(output_conn, contp, output_reader, INT64_MAX);
251 
252     string fetcher_tag, vars_tag, expr_tag, proc_tag, gzip_tag, gunzip_tag;
253     if (!data_fetcher) {
254       data_fetcher = new HttpDataFetcherImpl(contp, client_addr, createDebugTag(FETCHER_DEBUG_TAG, contp, fetcher_tag));
255     }
256     if (!esi_vars) {
257       esi_vars = new Variables(createDebugTag(VARS_DEBUG_TAG, contp, vars_tag), &TSDebug, &TSError, gWhitelistCookies);
258     }
259 
260     esi_proc = new EsiProcessor(
261       createDebugTag(PROCESSOR_DEBUG_TAG, contp, proc_tag), createDebugTag(PARSER_DEBUG_TAG, contp, fetcher_tag),
262       createDebugTag(EXPR_DEBUG_TAG, contp, expr_tag), &TSDebug, &TSError, *data_fetcher, *esi_vars, *gHandlerManager);
263 
264     esi_gzip   = new EsiGzip(createDebugTag(GZIP_DEBUG_TAG, contp, gzip_tag), &TSDebug, &TSError);
265     esi_gunzip = new EsiGunzip(createDebugTag(GUNZIP_DEBUG_TAG, contp, gunzip_tag), &TSDebug, &TSError);
266 
267     TSDebug(debug_tag, "[%s] Set input data type to [%s]", __FUNCTION__, DATA_TYPE_NAMES_[input_type]);
268 
269     retval = true;
270   } else {
271     TSDebug(debug_tag, "[%s] Transformation closed during initialization; Returning false", __FUNCTION__);
272   }
273 
274 lReturn:
275   initialized = true;
276   return retval;
277 }
278 
279 void
getClientState()280 ContData::getClientState()
281 {
282   TSMBuffer req_bufp;
283   TSMLoc req_hdr_loc;
284   if (TSHttpTxnClientReqGet(txnp, &req_bufp, &req_hdr_loc) != TS_SUCCESS) {
285     TSError("[esi][%s] Error while retrieving client request", __FUNCTION__);
286     return;
287   }
288 
289   if (!esi_vars) {
290     string vars_tag;
291     esi_vars = new Variables(createDebugTag(VARS_DEBUG_TAG, contp, vars_tag), &TSDebug, &TSError, gWhitelistCookies);
292   }
293   if (!data_fetcher) {
294     string fetcher_tag;
295     data_fetcher = new HttpDataFetcherImpl(contp, client_addr, createDebugTag(FETCHER_DEBUG_TAG, contp, fetcher_tag));
296   }
297   if (req_bufp && req_hdr_loc) {
298     TSMBuffer bufp;
299     TSMLoc url_loc;
300     if (TSHttpTxnPristineUrlGet(txnp, &bufp, &url_loc) != TS_SUCCESS) {
301       TSError("[esi][%s] Error while retrieving hdr url", __FUNCTION__);
302       return;
303     }
304     if (url_loc) {
305       if (request_url) {
306         TSfree(request_url);
307       }
308       int length;
309       request_url = TSUrlStringGet(bufp, url_loc, &length);
310       TSDebug(DEBUG_TAG, "[%s] Got request URL [%s]", __FUNCTION__, request_url ? request_url : "(null)");
311       int query_len;
312       const char *query = TSUrlHttpQueryGet(bufp, url_loc, &query_len);
313       if (query) {
314         esi_vars->populate(query, query_len);
315       }
316       TSHandleMLocRelease(bufp, req_hdr_loc, url_loc);
317     }
318     TSMLoc field_loc = TSMimeHdrFieldGet(req_bufp, req_hdr_loc, 0);
319     while (field_loc) {
320       TSMLoc next_field_loc;
321       const char *name;
322       int name_len;
323 
324       name = TSMimeHdrFieldNameGet(req_bufp, req_hdr_loc, field_loc, &name_len);
325       if (name) {
326         int n_values;
327         n_values = TSMimeHdrFieldValuesCount(req_bufp, req_hdr_loc, field_loc);
328         if (n_values && (n_values != TS_ERROR)) {
329           const char *value = nullptr;
330           int value_len     = 0;
331           if (n_values == 1) {
332             value = TSMimeHdrFieldValueStringGet(req_bufp, req_hdr_loc, field_loc, 0, &value_len);
333 
334             if (nullptr != value && value_len) {
335               if (Utils::areEqual(name, name_len, TS_MIME_FIELD_ACCEPT_ENCODING, TS_MIME_LEN_ACCEPT_ENCODING) &&
336                   Utils::areEqual(value, value_len, TS_HTTP_VALUE_GZIP, TS_HTTP_LEN_GZIP)) {
337                 gzip_output = true;
338               }
339             }
340           } else {
341             for (int i = 0; i < n_values; ++i) {
342               value = TSMimeHdrFieldValueStringGet(req_bufp, req_hdr_loc, field_loc, i, &value_len);
343               if (nullptr != value && value_len) {
344                 if (Utils::areEqual(name, name_len, TS_MIME_FIELD_ACCEPT_ENCODING, TS_MIME_LEN_ACCEPT_ENCODING) &&
345                     Utils::areEqual(value, value_len, TS_HTTP_VALUE_GZIP, TS_HTTP_LEN_GZIP)) {
346                   gzip_output = true;
347                 }
348               }
349             }
350 
351             value = TSMimeHdrFieldValueStringGet(req_bufp, req_hdr_loc, field_loc, -1, &value_len);
352           }
353 
354           if (value != nullptr) {
355             HttpHeader header(name, name_len, value, value_len);
356             data_fetcher->useHeader(header);
357             esi_vars->populate(header);
358           }
359         }
360       }
361 
362       next_field_loc = TSMimeHdrFieldNext(req_bufp, req_hdr_loc, field_loc);
363       TSHandleMLocRelease(req_bufp, req_hdr_loc, field_loc);
364       field_loc = next_field_loc;
365     }
366   }
367 
368   if (gzip_output) {
369     if (option_info->disable_gzip_output) {
370       TSDebug(DEBUG_TAG, "[%s] disable gzip output", __FUNCTION__);
371       gzip_output = false;
372     } else {
373       TSDebug(DEBUG_TAG, "[%s] Client accepts gzip encoding; will compress output", __FUNCTION__);
374     }
375   }
376 
377   TSHandleMLocRelease(req_bufp, TS_NULL_MLOC, req_hdr_loc);
378 }
379 
380 void
fillPostHeader(TSMBuffer bufp,TSMLoc hdr_loc)381 ContData::fillPostHeader(TSMBuffer bufp, TSMLoc hdr_loc)
382 {
383   int n_mime_headers = TSMimeHdrFieldsCount(bufp, hdr_loc);
384   TSMLoc field_loc;
385   const char *name, *value;
386   int name_len, value_len;
387   string header;
388   for (int i = 0; i < n_mime_headers; ++i) {
389     field_loc = TSMimeHdrFieldGet(bufp, hdr_loc, i);
390     if (!field_loc) {
391       TSDebug(DEBUG_TAG, "[%s] Error while obtaining header field #%d", __FUNCTION__, i);
392       continue;
393     }
394     name = TSMimeHdrFieldNameGet(bufp, hdr_loc, field_loc, &name_len);
395     if (name) {
396       if (Utils::areEqual(name, name_len, TS_MIME_FIELD_TRANSFER_ENCODING, TS_MIME_LEN_TRANSFER_ENCODING)) {
397         TSDebug(DEBUG_TAG, "[%s] Not retaining transfer encoding header", __FUNCTION__);
398       } else if (Utils::areEqual(name, name_len, MIME_FIELD_XESI, MIME_FIELD_XESI_LEN)) {
399         TSDebug(DEBUG_TAG, "[%s] Not retaining 'X-Esi' header", __FUNCTION__);
400       } else if (Utils::areEqual(name, name_len, TS_MIME_FIELD_CONTENT_LENGTH, TS_MIME_LEN_CONTENT_LENGTH)) {
401         TSDebug(DEBUG_TAG, "[%s] Not retaining 'Content-length' header", __FUNCTION__);
402       } else {
403         header.assign(name, name_len);
404         header.append(": ");
405         int n_field_values = TSMimeHdrFieldValuesCount(bufp, hdr_loc, field_loc);
406         for (int j = 0; j < n_field_values; ++j) {
407           value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, j, &value_len);
408           if (nullptr == value || !value_len) {
409             TSDebug(DEBUG_TAG, "[%s] Error while getting value #%d of header [%.*s]", __FUNCTION__, j, name_len, name);
410           } else {
411             if (Utils::areEqual(name, name_len, TS_MIME_FIELD_VARY, TS_MIME_LEN_VARY) &&
412                 Utils::areEqual(value, value_len, TS_MIME_FIELD_ACCEPT_ENCODING, TS_MIME_LEN_ACCEPT_ENCODING)) {
413               TSDebug(DEBUG_TAG, "[%s] Not retaining 'vary: accept-encoding' header", __FUNCTION__);
414             } else if (Utils::areEqual(name, name_len, TS_MIME_FIELD_CONTENT_ENCODING, TS_MIME_LEN_CONTENT_ENCODING) &&
415                        Utils::areEqual(value, value_len, TS_HTTP_VALUE_GZIP, TS_HTTP_LEN_GZIP)) {
416               TSDebug(DEBUG_TAG, "[%s] Not retaining 'content-encoding: gzip' header", __FUNCTION__);
417             } else {
418               if (header[header.size() - 2] != ':') {
419                 header.append(", ");
420               }
421               header.append(value, value_len);
422               checkForCacheHeader(name, name_len, value, value_len, os_response_cacheable);
423               if (!os_response_cacheable) {
424                 TSDebug(DEBUG_TAG, "[%s] Header [%.*s] with value [%.*s] is a no-cache header", __FUNCTION__, name_len, name,
425                         value_len, value);
426                 break;
427               }
428             }
429           } // end if got value string
430         }   // end value iteration
431 
432         if (static_cast<int>(header.size()) > (name_len + 2 /* for ': ' */)) {
433           header.append("\r\n");
434           post_headers.push_back(header);
435         }
436       } // end if processable header
437     }   // end if got header name
438     TSHandleMLocRelease(bufp, hdr_loc, field_loc);
439     if (!os_response_cacheable) {
440       post_headers.clear();
441       break;
442     }
443   } // end header iteration
444 }
445 
446 void
getServerState()447 ContData::getServerState()
448 {
449   TSMBuffer bufp;
450   TSMLoc hdr_loc;
451 
452   if (cache_txn) {
453     if (intercept_header) {
454       input_type = DATA_TYPE_PACKED_ESI;
455       return;
456     } else if (TSHttpTxnCachedRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
457       TSError("[esi][%s] Could not get server response; set input type to RAW_ESI", __FUNCTION__);
458       input_type = DATA_TYPE_RAW_ESI;
459       return;
460     }
461   } else if (TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
462     TSError("[esi][%s] Could not get server response; set input type to RAW_ESI", __FUNCTION__);
463     input_type = DATA_TYPE_RAW_ESI;
464     return;
465   }
466 
467   if (checkHeaderValue(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_ENCODING, TS_MIME_LEN_CONTENT_ENCODING, TS_HTTP_VALUE_GZIP,
468                        TS_HTTP_LEN_GZIP)) {
469     input_type = DATA_TYPE_GZIPPED_ESI;
470   } else {
471     input_type = DATA_TYPE_RAW_ESI;
472   }
473 
474   if (option_info->packed_node_support && !cache_txn && !head_only) {
475     fillPostHeader(bufp, hdr_loc);
476   }
477 
478   TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
479 }
480 
~ContData()481 ContData::~ContData()
482 {
483   TSDebug(debug_tag, "[%s] Destroying continuation data", __FUNCTION__);
484   if (output_reader) {
485     TSIOBufferReaderFree(output_reader);
486   }
487   if (output_buffer) {
488     TSIOBufferDestroy(output_buffer);
489   }
490   if (request_url) {
491     TSfree(request_url);
492   }
493   if (esi_vars) {
494     delete esi_vars;
495   }
496   if (data_fetcher) {
497     delete data_fetcher;
498   }
499   if (esi_proc) {
500     delete esi_proc;
501   }
502   if (esi_gzip) {
503     delete esi_gzip;
504   }
505   if (esi_gunzip) {
506     delete esi_gunzip;
507   }
508 }
509 
510 static int
removeCacheHandler(TSCont contp,TSEvent,void *)511 removeCacheHandler(TSCont contp, TSEvent /* event ATS_UNUSED */, void * /* edata ATS_UNUSED */)
512 {
513   // TSDebug(DEBUG_TAG, "[%s] event: %d", __FUNCTION__, (int)event);
514   TSContDestroy(contp);
515   // just ignore cache remove message
516   return 0;
517 }
518 
519 static bool
removeCacheKey(TSHttpTxn txnp)520 removeCacheKey(TSHttpTxn txnp)
521 {
522   TSMBuffer req_bufp;
523   TSMLoc req_hdr_loc;
524   TSMLoc url_loc      = nullptr;
525   TSCont contp        = nullptr;
526   TSCacheKey cacheKey = nullptr;
527   bool result         = false;
528 
529   if (TSHttpTxnClientReqGet(txnp, &req_bufp, &req_hdr_loc) != TS_SUCCESS) {
530     TSError("[esi][%s] Error while retrieving client request", __FUNCTION__);
531     return false;
532   }
533 
534   do {
535     if (TSHttpTxnPristineUrlGet(txnp, &req_bufp, &url_loc) != TS_SUCCESS) {
536       TSError("[esi][%s] Error while retrieving hdr url", __FUNCTION__);
537       break;
538     }
539 
540     contp = TSContCreate(removeCacheHandler, nullptr);
541     if (contp == nullptr) {
542       TSError("[esi][%s] Could not create continuation", __FUNCTION__);
543       break;
544     }
545 
546     cacheKey = TSCacheKeyCreate();
547     if (cacheKey == nullptr) {
548       TSError("[esi][%s] TSCacheKeyCreate fail", __FUNCTION__);
549       break;
550     }
551 
552     if (TSCacheKeyDigestFromUrlSet(cacheKey, url_loc) != TS_SUCCESS) {
553       TSError("[esi][%s] TSCacheKeyDigestFromUrlSet fail", __FUNCTION__);
554       break;
555     }
556 
557     TSCacheRemove(contp, cacheKey);
558     result = true;
559     TSError("[esi][%s] TSCacheRemoved", __FUNCTION__);
560   } while (false);
561 
562   if (cacheKey != nullptr) {
563     TSCacheKeyDestroy(cacheKey);
564   }
565   if (!result) {
566     if (contp != nullptr) {
567       TSContDestroy(contp);
568     }
569   }
570 
571   TSHandleMLocRelease(req_bufp, req_hdr_loc, url_loc);
572   if (req_hdr_loc != nullptr) {
573     TSHandleMLocRelease(req_bufp, TS_NULL_MLOC, req_hdr_loc);
574   }
575 
576   return result;
577 }
578 
579 static void
cacheNodeList(ContData * cont_data)580 cacheNodeList(ContData *cont_data)
581 {
582   if (TSHttpTxnAborted(cont_data->txnp) == TS_SUCCESS) {
583     TSDebug(cont_data->debug_tag, "[%s] Not caching node list as txn has been aborted", __FUNCTION__);
584     return;
585   }
586   string post_request("");
587   post_request.append(TS_HTTP_METHOD_POST);
588   post_request.append(" ");
589   post_request.append(cont_data->request_url);
590   post_request.append(" HTTP/1.0\r\n");
591   post_request.append(SERVER_INTERCEPT_HEADER);
592   post_request.append(": cache=1\r\n");
593   for (list<string>::iterator list_iter = cont_data->post_headers.begin(); list_iter != cont_data->post_headers.end();
594        ++list_iter) {
595     post_request.append(ECHO_HEADER_PREFIX);
596 
597     // TSDebug(cont_data->debug_tag, "[%s] header == %s", __FUNCTION__, list_iter->c_str());
598     if (((int)list_iter->length() > HEADER_MASK_PREFIX_SIZE) &&
599         (strncmp(list_iter->c_str(), HEADER_MASK_PREFIX, HEADER_MASK_PREFIX_SIZE) == 0)) {
600       post_request.append(list_iter->c_str() + HEADER_MASK_PREFIX_SIZE, list_iter->length() - HEADER_MASK_PREFIX_SIZE);
601     } else {
602       post_request.append(*list_iter);
603     }
604   }
605   post_request.append(TS_MIME_FIELD_ACCEPT_ENCODING, TS_MIME_LEN_ACCEPT_ENCODING);
606   post_request.append(": ");
607   post_request.append(TS_HTTP_VALUE_GZIP, TS_HTTP_LEN_GZIP);
608   post_request.append("\r\n");
609 
610   string body("");
611   cont_data->esi_proc->packNodeList(body, false);
612   char buf[64];
613   snprintf(buf, 64, "%s: %d\r\n\r\n", TS_MIME_FIELD_CONTENT_LENGTH, (int)body.size());
614 
615   post_request.append(buf);
616   post_request.append(body);
617 
618   // TSError("[%s] DO caching node list size=%d", __FUNCTION__, (int)body.size());
619   // TSDebug(cont_data->debug_tag, "[%s] caching node list size=%d", __FUNCTION__, (int)body.size());
620 
621   TSFetchEvent event_ids = {0, 0, 0};
622   TSFetchUrl(post_request.data(), post_request.size(), cont_data->client_addr, cont_data->contp, NO_CALLBACK, event_ids);
623 }
624 
625 static int
transformData(TSCont contp)626 transformData(TSCont contp)
627 {
628   ContData *cont_data;
629   int64_t toread, consumed = 0, avail;
630   bool input_vio_buf_null     = false;
631   bool process_input_complete = false;
632 
633   // Get the output (downstream) vconnection where we'll write data to.
634   cont_data = static_cast<ContData *>(TSContDataGet(contp));
635 
636   // If the input VIO's buffer is NULL, we need to terminate the transformation
637   if (!TSVIOBufferGet(cont_data->input_vio)) {
638     input_vio_buf_null = true;
639     if (cont_data->curr_state == ContData::PROCESSING_COMPLETE) {
640       TSDebug(cont_data->debug_tag, "[%s] input_vio NULL, marking transformation to be terminated", __FUNCTION__);
641       return 1;
642     } else if (cont_data->curr_state == ContData::READING_ESI_DOC) {
643       TSDebug(cont_data->debug_tag, "[%s] input_vio NULL while in read state. Assuming end of input", __FUNCTION__);
644       process_input_complete = true;
645     } else {
646       if (!cont_data->data_fetcher->isFetchComplete()) {
647         TSDebug(cont_data->debug_tag, "[%s] input_vio NULL, but data needs to be fetched. Returning control", __FUNCTION__);
648         if (!cont_data->option_info->first_byte_flush) {
649           return 1;
650         }
651       } else {
652         TSDebug(cont_data->debug_tag, "[%s] input_vio NULL, but processing needs to (and can) be completed", __FUNCTION__);
653       }
654     }
655   }
656 
657   if (!process_input_complete && (cont_data->curr_state == ContData::READING_ESI_DOC)) {
658     // Determine how much data we have left to read.
659     toread = TSVIONTodoGet(cont_data->input_vio);
660     TSDebug(cont_data->debug_tag, "[%s] upstream VC has %" PRId64 " bytes available to read", __FUNCTION__, toread);
661 
662     if (toread > 0) {
663       avail = TSIOBufferReaderAvail(cont_data->input_reader);
664       if (avail == TS_ERROR) {
665         TSError("[esi][%s] Error while getting number of bytes available", __FUNCTION__);
666         return 0;
667       }
668 
669       // There are some data available for reading. Let's parse it
670       if (avail > 0) {
671         int64_t data_len;
672         const char *data;
673         TSIOBufferBlock block = TSIOBufferReaderStart(cont_data->input_reader);
674         // Now start extraction
675         while (block != nullptr) {
676           data = TSIOBufferBlockReadStart(block, cont_data->input_reader, &data_len);
677           if (cont_data->input_type == DATA_TYPE_RAW_ESI) {
678             cont_data->esi_proc->addParseData(data, data_len);
679           } else if (cont_data->input_type == DATA_TYPE_GZIPPED_ESI) {
680             string udata = "";
681             cont_data->esi_gunzip->stream_decode(data, data_len, udata);
682             cont_data->esi_proc->addParseData(udata.data(), udata.size());
683           } else {
684             cont_data->packed_node_list.append(data, data_len);
685           }
686           TSDebug(cont_data->debug_tag, "[%s] Added chunk of %" PRId64 " bytes starting with [%.10s] to parse list", __FUNCTION__,
687                   data_len, (data_len ? data : "(null)"));
688           consumed += data_len;
689 
690           block = TSIOBufferBlockNext(block);
691         }
692       }
693       TSDebug(cont_data->debug_tag, "[%s] Consumed %" PRId64 " bytes from upstream VC", __FUNCTION__, consumed);
694 
695       TSIOBufferReaderConsume(cont_data->input_reader, consumed);
696 
697       // Modify the input VIO to reflect how much data we've completed.
698       TSVIONDoneSet(cont_data->input_vio, TSVIONDoneGet(cont_data->input_vio) + consumed);
699 
700       toread = TSVIONTodoGet(cont_data->input_vio); // set this for the test after this if block
701     }
702 
703     if (toread > 0) { // testing this again because it might have changed in previous if block
704       // let upstream know we are ready to read new data
705       TSContCall(TSVIOContGet(cont_data->input_vio), TS_EVENT_VCONN_WRITE_READY, cont_data->input_vio);
706     } else {
707       // we have consumed everything that there was to read
708       process_input_complete = true;
709     }
710   }
711   if (process_input_complete) {
712     TSDebug(cont_data->debug_tag, "[%s] Completed reading input", __FUNCTION__);
713     if (cont_data->input_type == DATA_TYPE_PACKED_ESI) {
714       TSDebug(DEBUG_TAG, "[%s] Going to use packed node list of size %d", __FUNCTION__,
715               static_cast<int>(cont_data->packed_node_list.size()));
716       if (cont_data->esi_proc->usePackedNodeList(cont_data->packed_node_list) == EsiProcessor::UNPACK_FAILURE) {
717         removeCacheKey(cont_data->txnp);
718 
719         cont_data->input_type = DATA_TYPE_RAW_ESI;
720         cont_data->esi_proc->start();
721         cont_data->esi_proc->addParseData(cont_data->packed_node_list.data(), cont_data->packed_node_list.size());
722       }
723     }
724 
725     if (cont_data->input_type != DATA_TYPE_PACKED_ESI) {
726       bool gunzip_complete = true;
727       if (cont_data->input_type == DATA_TYPE_GZIPPED_ESI) {
728         gunzip_complete = cont_data->esi_gunzip->stream_finish();
729       }
730       if (cont_data->esi_proc->completeParse() && gunzip_complete) {
731         if (cont_data->option_info->packed_node_support && cont_data->os_response_cacheable && !cont_data->cache_txn &&
732             !cont_data->head_only) {
733           cacheNodeList(cont_data);
734         }
735       }
736     }
737 
738     cont_data->curr_state = ContData::FETCHING_DATA;
739     if (!input_vio_buf_null) {
740       TSContCall(TSVIOContGet(cont_data->input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, cont_data->input_vio);
741     }
742   }
743 
744   if ((cont_data->curr_state == ContData::FETCHING_DATA) &&
745       (!cont_data->option_info->first_byte_flush)) { // retest as state may have changed in previous block
746     if (cont_data->data_fetcher->isFetchComplete()) {
747       TSDebug(cont_data->debug_tag, "[%s] data ready; going to process doc", __FUNCTION__);
748       const char *out_data;
749       int out_data_len;
750       EsiProcessor::ReturnCode retval = cont_data->esi_proc->process(out_data, out_data_len);
751       TSDebug(cont_data->debug_tag, "[%s] data length: %d, retval: %d", __FUNCTION__, out_data_len, retval);
752       if (retval == EsiProcessor::NEED_MORE_DATA) {
753         TSDebug(cont_data->debug_tag,
754                 "[%s] ESI processor needs more data; "
755                 "will wait for all data to be fetched",
756                 __FUNCTION__);
757         return 1;
758       }
759       cont_data->curr_state = ContData::PROCESSING_COMPLETE;
760       if (retval == EsiProcessor::SUCCESS) {
761         TSDebug(cont_data->debug_tag, "[%s] ESI processor output document of size %d starting with [%.10s]", __FUNCTION__,
762                 out_data_len, (out_data_len ? out_data : "(null)"));
763       } else {
764         TSError("[esi][%s] ESI processor failed to process document; will return empty document", __FUNCTION__);
765         out_data     = "";
766         out_data_len = 0;
767       }
768 
769       // make sure transformation has not been prematurely terminated
770       if (!cont_data->xform_closed) {
771         string cdata;
772         if (cont_data->gzip_output) {
773           if (!gzip(out_data, out_data_len, cdata)) {
774             TSError("[esi][%s] Error while gzipping content", __FUNCTION__);
775             out_data_len = 0;
776             out_data     = "";
777           } else {
778             TSDebug(cont_data->debug_tag, "[%s] Compressed document from size %d to %d bytes", __FUNCTION__, out_data_len,
779                     static_cast<int>(cdata.size()));
780             out_data_len = cdata.size();
781             out_data     = cdata.data();
782           }
783         }
784 
785         // Get downstream VIO
786         TSVConn output_conn;
787         output_conn = TSTransformOutputVConnGet(contp);
788         if (!output_conn) {
789           TSError("[esi][%s] Error while getting transform VC", __FUNCTION__);
790           return 0;
791         }
792 
793         TSVIO output_vio;
794         output_vio = TSVConnWrite(output_conn, contp, cont_data->output_reader, out_data_len);
795 
796         if (TSIOBufferWrite(TSVIOBufferGet(output_vio), out_data, out_data_len) == TS_ERROR) {
797           TSError("[esi][%s] Error while writing bytes to downstream VC", __FUNCTION__);
798           return 0;
799         }
800 
801         TSVIONBytesSet(output_vio, out_data_len);
802 
803         // Reenable the output connection so it can read the data we've produced.
804         TSVIOReenable(output_vio);
805       }
806     } else {
807       TSDebug(cont_data->debug_tag, "[%s] Data not available yet; cannot process document", __FUNCTION__);
808     }
809   }
810 
811   if (((cont_data->curr_state == ContData::FETCHING_DATA) || (cont_data->curr_state == ContData::READING_ESI_DOC)) &&
812       (cont_data->option_info->first_byte_flush)) { // retest as state may have changed in previous block
813     TSDebug(cont_data->debug_tag, "[%s] trying to process doc", __FUNCTION__);
814     string out_data;
815     string cdata;
816     int overall_len;
817     EsiProcessor::ReturnCode retval = cont_data->esi_proc->flush(out_data, overall_len);
818 
819     if ((cont_data->curr_state == ContData::FETCHING_DATA) && cont_data->data_fetcher->isFetchComplete()) {
820       TSDebug(cont_data->debug_tag, "[%s] data ready; last process() will have finished the entire processing", __FUNCTION__);
821       cont_data->curr_state = ContData::PROCESSING_COMPLETE;
822     }
823 
824     if (retval == EsiProcessor::SUCCESS) {
825       TSDebug(cont_data->debug_tag, "[%s] ESI processor output document of size %d starting with [%.10s]", __FUNCTION__,
826               static_cast<int>(out_data.size()), (out_data.size() ? out_data.data() : "(null)"));
827     } else {
828       TSError("[esi][%s] ESI processor failed to process document; will return empty document", __FUNCTION__);
829       out_data.assign("");
830 
831       if (!cont_data->xform_closed) {
832         TSVIONBytesSet(cont_data->output_vio, 0);
833         TSVIOReenable(cont_data->output_vio);
834       }
835     }
836 
837     // make sure transformation has not been prematurely terminated
838     if (!cont_data->xform_closed && out_data.size() > 0) {
839       if (cont_data->gzip_output) {
840         if (!cont_data->esi_gzip->stream_encode(out_data, cdata)) {
841           TSError("[esi][%s] Error while gzipping content", __FUNCTION__);
842         } else {
843           TSDebug(cont_data->debug_tag, "[%s] Compressed document from size %d to %d bytes", __FUNCTION__,
844                   static_cast<int>(out_data.size()), static_cast<int>(cdata.size()));
845         }
846       }
847 
848       if (cont_data->gzip_output) {
849         if (TSIOBufferWrite(TSVIOBufferGet(cont_data->output_vio), cdata.data(), cdata.size()) == TS_ERROR) {
850           TSError("[esi][%s] Error while writing bytes to downstream VC", __FUNCTION__);
851           return 0;
852         }
853       } else {
854         if (TSIOBufferWrite(TSVIOBufferGet(cont_data->output_vio), out_data.data(), out_data.size()) == TS_ERROR) {
855           TSError("[esi][%s] Error while writing bytes to downstream VC", __FUNCTION__);
856           return 0;
857         }
858       }
859     }
860     if (!cont_data->xform_closed) {
861       // should not set any fixed length
862       if (cont_data->curr_state == ContData::PROCESSING_COMPLETE) {
863         if (cont_data->gzip_output) {
864           string cdata;
865           int downstream_length;
866           if (!cont_data->esi_gzip->stream_finish(cdata, downstream_length)) {
867             TSError("[esi][%s] Error while finishing gzip", __FUNCTION__);
868             return 0;
869           } else {
870             if (TSVIOBufferGet(cont_data->output_vio) == nullptr) {
871               TSError("[esi][%s] Error while writing bytes to downstream VC", __FUNCTION__);
872               return 0;
873             }
874             if (TSIOBufferWrite(TSVIOBufferGet(cont_data->output_vio), cdata.data(), cdata.size()) == TS_ERROR) {
875               TSError("[esi][%s] Error while writing bytes to downstream VC", __FUNCTION__);
876               return 0;
877             }
878             TSDebug(cont_data->debug_tag, "[%s] ESI processed overall/gzip: %d", __FUNCTION__, downstream_length);
879             TSVIONBytesSet(cont_data->output_vio, downstream_length);
880           }
881         } else {
882           TSDebug(cont_data->debug_tag, "[%s] ESI processed overall: %d", __FUNCTION__, overall_len);
883           TSVIONBytesSet(cont_data->output_vio, overall_len);
884         }
885       }
886 
887       // Reenable the output connection so it can read the data we've produced.
888       TSVIOReenable(cont_data->output_vio);
889     }
890   }
891 
892   return 1;
893 }
894 
895 static int
transformHandler(TSCont contp,TSEvent event,void * edata)896 transformHandler(TSCont contp, TSEvent event, void *edata)
897 {
898   TSVIO input_vio;
899   ContData *cont_data;
900   cont_data = static_cast<ContData *>(TSContDataGet(contp));
901 
902   // we need these later, but declaring now avoid compiler warning w.r.t. goto
903   bool process_event = true;
904   const char *cont_debug_tag;
905   bool shutdown, is_fetch_event;
906 
907   if (!cont_data->initialized) {
908     if (!cont_data->init()) {
909       TSError("[esi][%s] Could not initialize continuation data; shutting down transformation", __FUNCTION__);
910       goto lShutdown;
911     }
912     TSDebug(cont_data->debug_tag, "[%s] initialized continuation data", __FUNCTION__);
913   }
914 
915   cont_debug_tag = cont_data->debug_tag; // just a handy reference
916 
917   cont_data->checkXformStatus();
918 
919   is_fetch_event = cont_data->data_fetcher->isFetchEvent(event);
920 
921   if (cont_data->xform_closed) {
922     TSDebug(cont_debug_tag, "[%s] Transformation closed, post-processing", __FUNCTION__);
923     if (cont_data->curr_state == ContData::PROCESSING_COMPLETE) {
924       TSDebug(cont_debug_tag, "[%s] Processing is complete, not processing current event %d", __FUNCTION__, event);
925       process_event = false;
926     } else if (cont_data->curr_state == ContData::READING_ESI_DOC) {
927       TSDebug(cont_debug_tag, "[%s] Parsing is incomplete, will force end of input", __FUNCTION__);
928       cont_data->curr_state = ContData::FETCHING_DATA;
929     }
930     if (cont_data->curr_state == ContData::FETCHING_DATA) { // retest as it may be modified in prev. if block
931       if (cont_data->data_fetcher->isFetchComplete()) {
932         TSDebug(cont_debug_tag, "[%s] Requested data has been fetched; will skip event and marking processing as complete ",
933                 __FUNCTION__);
934         cont_data->curr_state = ContData::PROCESSING_COMPLETE;
935         process_event         = false;
936       } else {
937         if (is_fetch_event) {
938           TSDebug(cont_debug_tag, "[%s] Going to process received data", __FUNCTION__);
939         } else {
940           // transformation is over, but data hasn't been fetched;
941           // let's wait for data to be fetched - we will be called
942           // by Fetch API and go through this loop again
943           TSDebug(cont_debug_tag, "[%s] Ignoring event %d; Will wait for pending data", __FUNCTION__, event);
944           process_event = false;
945         }
946       }
947     }
948   }
949 
950   if (process_event) {
951     switch (event) {
952     case TS_EVENT_ERROR:
953       // doubt: what is this code doing?
954       input_vio = TSVConnWriteVIOGet(contp);
955       if (!input_vio) {
956         TSError("[esi][%s] Error while getting upstream vio", __FUNCTION__);
957       } else {
958         TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio);
959       }
960       // FetchSM also might send this; let's just output whatever we have
961       cont_data->curr_state = ContData::FETCHING_DATA;
962       transformData(contp);
963       break;
964 
965     case TS_EVENT_VCONN_WRITE_READY:
966       TSDebug(cont_debug_tag, "[%s] WRITE_READY", __FUNCTION__);
967       if (!cont_data->option_info->first_byte_flush) {
968         TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
969       }
970       break;
971 
972     case TS_EVENT_VCONN_WRITE_COMPLETE:
973       TSDebug(cont_debug_tag, "[%s] shutting down transformation", __FUNCTION__);
974       TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
975       break;
976 
977     case TS_EVENT_IMMEDIATE:
978       TSDebug(cont_debug_tag, "[%s] handling TS_EVENT_IMMEDIATE", __FUNCTION__);
979       transformData(contp);
980       break;
981 
982     default:
983       if (is_fetch_event) {
984         TSDebug(cont_debug_tag, "[%s] Handling fetch event %d", __FUNCTION__, event);
985         if (cont_data->data_fetcher->handleFetchEvent(event, edata)) {
986           if ((cont_data->curr_state == ContData::FETCHING_DATA) || (cont_data->curr_state == ContData::READING_ESI_DOC)) {
987             // there's a small chance that fetcher is ready even before
988             // parsing is complete; hence we need to check the state too
989             if (cont_data->option_info->first_byte_flush || cont_data->data_fetcher->isFetchComplete()) {
990               TSDebug(cont_debug_tag, "[%s] fetcher is ready with data, going into process stage", __FUNCTION__);
991               transformData(contp);
992             }
993           }
994         } else {
995           TSError("[esi][%s] Could not handle fetch event!", __FUNCTION__);
996         }
997       } else {
998         TSAssert(!"Unexpected event");
999       }
1000       break;
1001     }
1002   }
1003 
1004   TSDebug(cont_data->debug_tag, "[%s] transformHandler, event: %d, curr_state: %d", __FUNCTION__, static_cast<int>(event),
1005           static_cast<int>(cont_data->curr_state));
1006 
1007   shutdown = (cont_data->xform_closed && (cont_data->curr_state == ContData::PROCESSING_COMPLETE));
1008   if (shutdown) {
1009     if (process_event && is_fetch_event) {
1010       // we need to return control to the fetch API to give up it's
1011       // lock on our continuation which will fail if we destroy
1012       // ourselves right now
1013       TSDebug(cont_debug_tag, "[%s] Deferring shutdown as data event was just processed", __FUNCTION__);
1014       TSContScheduleOnPool(contp, 10, TS_THREAD_POOL_TASK);
1015     } else {
1016       goto lShutdown;
1017     }
1018   }
1019 
1020   return 1;
1021 
1022 lShutdown:
1023   TSDebug(cont_data->debug_tag, "[%s] transformation closed; cleaning up data", __FUNCTION__);
1024   delete cont_data;
1025   TSContDestroy(contp);
1026   return 1;
1027 }
1028 
1029 struct RespHdrModData {
1030   bool cache_txn;
1031   bool gzip_encoding;
1032   bool head_only;
1033   const struct OptionInfo *option_info;
1034 };
1035 
1036 static void
addMimeHeaderField(TSMBuffer bufp,TSMLoc hdr_loc,const char * name,int name_len,const char * value,int value_len)1037 addMimeHeaderField(TSMBuffer bufp, TSMLoc hdr_loc, const char *name, int name_len, const char *value, int value_len)
1038 {
1039   TSMLoc field_loc = (TSMLoc) nullptr;
1040   TSMimeHdrFieldCreate(bufp, hdr_loc, &field_loc);
1041   if (!field_loc) {
1042     TSError("[esi][%s] Error while creating mime field", __FUNCTION__);
1043   } else {
1044     if (TSMimeHdrFieldNameSet(bufp, hdr_loc, field_loc, name, name_len) != TS_SUCCESS) {
1045       TSError("[esi][%s] Error while setting name [%.*s] for MIME header field", __FUNCTION__, name_len, name);
1046     } else {
1047       if (TSMimeHdrFieldValueStringInsert(bufp, hdr_loc, field_loc, 0, value, value_len) != TS_SUCCESS) {
1048         TSError("[esi][%s] Error while inserting value [%.*s] string to MIME field [%.*s]", __FUNCTION__, value_len, value,
1049                 name_len, name);
1050       } else {
1051         if (TSMimeHdrFieldAppend(bufp, hdr_loc, field_loc) != TS_SUCCESS) {
1052           TSError("[esi][%s] Error while appending MIME field with name [%.*s] and value [%.*s]", __FUNCTION__, name_len, name,
1053                   value_len, value);
1054         }
1055       }
1056     }
1057     TSHandleMLocRelease(bufp, hdr_loc, field_loc);
1058   }
1059 }
1060 
1061 static int
modifyResponseHeader(TSCont contp,TSEvent event,void * edata)1062 modifyResponseHeader(TSCont contp, TSEvent event, void *edata)
1063 {
1064   int retval               = 0;
1065   RespHdrModData *mod_data = static_cast<RespHdrModData *>(TSContDataGet(contp));
1066   TSHttpTxn txnp           = static_cast<TSHttpTxn>(edata);
1067   if (event != TS_EVENT_HTTP_SEND_RESPONSE_HDR) {
1068     TSError("[esi][%s] Unexpected event (%d)", __FUNCTION__, event);
1069     goto lReturn;
1070   }
1071   TSMBuffer bufp;
1072   TSMLoc hdr_loc;
1073   if (TSHttpTxnClientRespGet(txnp, &bufp, &hdr_loc) == TS_SUCCESS) {
1074     int n_mime_headers = TSMimeHdrFieldsCount(bufp, hdr_loc);
1075     TSMLoc field_loc;
1076     const char *name, *value;
1077     int name_len, value_len;
1078     for (int i = 0; i < n_mime_headers; ++i) {
1079       field_loc = TSMimeHdrFieldGet(bufp, hdr_loc, i);
1080       if (!field_loc) {
1081         TSDebug(DEBUG_TAG, "[%s] Error while obtaining header field #%d", __FUNCTION__, i);
1082         continue;
1083       }
1084       name = TSMimeHdrFieldNameGet(bufp, hdr_loc, field_loc, &name_len);
1085       if (name) {
1086         bool destroy_header = false;
1087 
1088         if (Utils::areEqual(name, name_len, SERVER_INTERCEPT_HEADER, SERVER_INTERCEPT_HEADER_LEN)) {
1089           destroy_header = true;
1090         } else if (Utils::areEqual(name, name_len, TS_MIME_FIELD_AGE, TS_MIME_LEN_AGE)) {
1091           destroy_header = true;
1092         } else if (Utils::areEqual(name, name_len, MIME_FIELD_XESI, MIME_FIELD_XESI_LEN)) {
1093           destroy_header = true;
1094         } else if ((name_len > HEADER_MASK_PREFIX_SIZE) && (strncmp(name, HEADER_MASK_PREFIX, HEADER_MASK_PREFIX_SIZE) == 0)) {
1095           destroy_header = true;
1096         } else if (mod_data->option_info->private_response &&
1097                    (Utils::areEqual(name, name_len, TS_MIME_FIELD_CACHE_CONTROL, TS_MIME_LEN_CACHE_CONTROL) ||
1098                     Utils::areEqual(name, name_len, TS_MIME_FIELD_EXPIRES, TS_MIME_LEN_EXPIRES))) {
1099           destroy_header = true;
1100         } else if (Utils::areEqual(name, name_len, TS_MIME_FIELD_CONTENT_LENGTH, TS_MIME_LEN_CONTENT_LENGTH)) {
1101           if (mod_data->head_only) {
1102             destroy_header = true;
1103             TSDebug(DEBUG_TAG, "[%s] remove Content-Length", __FUNCTION__);
1104           }
1105         } else {
1106           int n_field_values = TSMimeHdrFieldValuesCount(bufp, hdr_loc, field_loc);
1107           for (int j = 0; j < n_field_values; ++j) {
1108             value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, j, &value_len);
1109             if (nullptr == value || !value_len) {
1110               TSDebug(DEBUG_TAG, "[%s] Error while getting value #%d of header [%.*s]", __FUNCTION__, j, name_len, name);
1111             } else {
1112               if (!mod_data->option_info->packed_node_support || mod_data->cache_txn) {
1113                 bool response_cacheable, is_cache_header;
1114                 is_cache_header = checkForCacheHeader(name, name_len, value, value_len, response_cacheable);
1115                 if (is_cache_header && response_cacheable) {
1116                   destroy_header = true;
1117                 }
1118               }
1119             } // if got valid value for header
1120           }   // end for
1121         }
1122         if (destroy_header) {
1123           TSDebug(DEBUG_TAG, "[%s] Removing header with name [%.*s]", __FUNCTION__, name_len, name);
1124           TSMimeHdrFieldDestroy(bufp, hdr_loc, field_loc);
1125           --n_mime_headers;
1126           --i;
1127         }
1128       }
1129       TSHandleMLocRelease(bufp, hdr_loc, field_loc);
1130     }
1131     if (mod_data->gzip_encoding && !checkHeaderValue(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_ENCODING, TS_MIME_LEN_CONTENT_ENCODING,
1132                                                      TS_HTTP_VALUE_GZIP, TS_HTTP_LEN_GZIP)) {
1133       addMimeHeaderField(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_ENCODING, TS_MIME_LEN_CONTENT_ENCODING, TS_HTTP_VALUE_GZIP,
1134                          TS_HTTP_LEN_GZIP);
1135     }
1136 
1137     if (mod_data->option_info->packed_node_support && mod_data->cache_txn) {
1138       addMimeHeaderField(bufp, hdr_loc, TS_MIME_FIELD_VARY, TS_MIME_LEN_VARY, TS_MIME_FIELD_ACCEPT_ENCODING,
1139                          TS_MIME_LEN_ACCEPT_ENCODING);
1140     }
1141 
1142     if (mod_data->option_info->private_response) {
1143       addMimeHeaderField(bufp, hdr_loc, TS_MIME_FIELD_EXPIRES, TS_MIME_LEN_EXPIRES, HTTP_VALUE_PRIVATE_EXPIRES,
1144                          sizeof(HTTP_VALUE_PRIVATE_EXPIRES) - 1);
1145       addMimeHeaderField(bufp, hdr_loc, TS_MIME_FIELD_CACHE_CONTROL, TS_MIME_LEN_CACHE_CONTROL, HTTP_VALUE_PRIVATE_CC,
1146                          sizeof(HTTP_VALUE_PRIVATE_CC) - 1);
1147     }
1148 
1149     TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
1150     TSDebug(DEBUG_TAG, "[%s] Inspected client-bound headers", __FUNCTION__);
1151     retval = 1;
1152   } else {
1153     TSError("[esi][%s] Error while getting response from txn", __FUNCTION__);
1154   }
1155 
1156 lReturn:
1157   delete mod_data;
1158   TSContDestroy(contp);
1159   TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
1160   return retval;
1161 }
1162 
1163 static bool
checkHeaderValue(TSMBuffer bufp,TSMLoc hdr_loc,const char * name,int name_len,const char * exp_value,int exp_value_len,bool prefix)1164 checkHeaderValue(TSMBuffer bufp, TSMLoc hdr_loc, const char *name, int name_len, const char *exp_value, int exp_value_len,
1165                  bool prefix)
1166 {
1167   TSMLoc field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, name, name_len);
1168   if (!field_loc) {
1169     return false;
1170   }
1171   bool retval = false;
1172   if (exp_value && exp_value_len) {
1173     const char *value;
1174     int value_len;
1175     int n_values = TSMimeHdrFieldValuesCount(bufp, hdr_loc, field_loc);
1176     for (int i = 0; i < n_values; ++i) {
1177       value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, i, &value_len);
1178       if (nullptr != value && value_len) {
1179         if (prefix) {
1180           if ((value_len >= exp_value_len) && (strncasecmp(value, exp_value, exp_value_len) == 0)) {
1181             retval = true;
1182           }
1183         } else if (Utils::areEqual(value, value_len, exp_value, exp_value_len)) {
1184           retval = true;
1185         }
1186       } else {
1187         TSDebug(DEBUG_TAG, "[%s] Error while getting value # %d of header [%.*s]", __FUNCTION__, i, name_len, name);
1188       }
1189       if (retval) {
1190         break;
1191       }
1192     }
1193   } else { // only presence required
1194     retval = true;
1195   }
1196   TSHandleMLocRelease(bufp, hdr_loc, field_loc);
1197   return retval;
1198 }
1199 
1200 static void
maskOsCacheHeaders(TSHttpTxn txnp)1201 maskOsCacheHeaders(TSHttpTxn txnp)
1202 {
1203   TSMBuffer bufp;
1204   TSMLoc hdr_loc;
1205   if (TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
1206     TSError("[esi][%s] Couldn't get server response from txn", __FUNCTION__);
1207     return;
1208   }
1209   int n_mime_headers = TSMimeHdrFieldsCount(bufp, hdr_loc);
1210   TSMLoc field_loc;
1211   const char *name, *value;
1212   int name_len, value_len, n_field_values;
1213   bool os_response_cacheable, is_cache_header, mask_header;
1214   string masked_name;
1215   os_response_cacheable = true;
1216   for (int i = 0; i < n_mime_headers; ++i) {
1217     field_loc = TSMimeHdrFieldGet(bufp, hdr_loc, i);
1218     if (!field_loc) {
1219       TSDebug(DEBUG_TAG, "[%s] Error while obtaining header field #%d", __FUNCTION__, i);
1220       continue;
1221     }
1222     name = TSMimeHdrFieldNameGet(bufp, hdr_loc, field_loc, &name_len);
1223     if (name) {
1224       mask_header = is_cache_header = false;
1225       n_field_values                = TSMimeHdrFieldValuesCount(bufp, hdr_loc, field_loc);
1226       for (int j = 0; j < n_field_values; ++j) {
1227         value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, j, &value_len);
1228         if (nullptr == value || !value_len) {
1229           TSDebug(DEBUG_TAG, "[%s] Error while getting value #%d of header [%.*s]", __FUNCTION__, j, name_len, name);
1230         } else {
1231           is_cache_header = checkForCacheHeader(name, name_len, value, value_len, os_response_cacheable);
1232           if (!os_response_cacheable) {
1233             break;
1234           }
1235           if (is_cache_header) {
1236             TSDebug(DEBUG_TAG, "[%s] Masking OS cache header [%.*s] with value [%.*s]. ", __FUNCTION__, name_len, name, value_len,
1237                     value);
1238             mask_header = true;
1239           }
1240         } // end if got value string
1241       }   // end value iteration
1242       if (mask_header) {
1243         masked_name.assign(HEADER_MASK_PREFIX);
1244         masked_name.append(name, name_len);
1245         if (TSMimeHdrFieldNameSet(bufp, hdr_loc, field_loc, masked_name.data(), masked_name.size()) != TS_SUCCESS) {
1246           TSError("[esi][%s] Couldn't rename header [%.*s]", __FUNCTION__, name_len, name);
1247         }
1248       }
1249     } // end if got header name
1250     TSHandleMLocRelease(bufp, hdr_loc, field_loc);
1251     if (!os_response_cacheable) {
1252       break;
1253     }
1254   } // end header iteration
1255   TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
1256 }
1257 
1258 static bool
isTxnTransformable(TSHttpTxn txnp,bool is_cache_txn,bool * intercept_header,bool * head_only)1259 isTxnTransformable(TSHttpTxn txnp, bool is_cache_txn, bool *intercept_header, bool *head_only)
1260 {
1261   //  We are only interested in transforming "200 OK" responses with a
1262   //  Content-Type: text/ header and with X-Esi header
1263 
1264   TSMBuffer bufp;
1265   TSMLoc hdr_loc;
1266   TSReturnCode header_obtained;
1267   bool retval = false;
1268 
1269   if (TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
1270     TSError("[esi][%s] Couldn't get txn header", __FUNCTION__);
1271     return false;
1272   }
1273 
1274   int method_len;
1275   const char *method;
1276   method = TSHttpHdrMethodGet(bufp, hdr_loc, &method_len);
1277   if (method == nullptr) {
1278     TSError("[esi][%s] Couldn't get method", __FUNCTION__);
1279     TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
1280     return false;
1281   }
1282 
1283   if (method_len >= TS_HTTP_LEN_HEAD && memcmp(method, TS_HTTP_METHOD_HEAD, TS_HTTP_LEN_HEAD) == 0) {
1284     *head_only = true;
1285   } else if (!(((method_len >= TS_HTTP_LEN_POST && memcmp(method, TS_HTTP_METHOD_POST, TS_HTTP_LEN_POST) == 0)) ||
1286                ((method_len >= TS_HTTP_LEN_GET && memcmp(method, TS_HTTP_METHOD_GET, TS_HTTP_LEN_GET) == 0)))) {
1287     TSDebug(DEBUG_TAG, "[%s] method %.*s will be ignored", __FUNCTION__, method_len, method);
1288     TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
1289     return false;
1290   }
1291   TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
1292 
1293   header_obtained = is_cache_txn ? TSHttpTxnCachedRespGet(txnp, &bufp, &hdr_loc) : TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc);
1294   if (header_obtained != TS_SUCCESS) {
1295     TSError("[esi][%s] Couldn't get txn header", __FUNCTION__);
1296     return false;
1297   }
1298 
1299   do {
1300     *intercept_header = checkHeaderValue(bufp, hdr_loc, SERVER_INTERCEPT_HEADER, SERVER_INTERCEPT_HEADER_LEN);
1301     if (*intercept_header) {
1302       if (is_cache_txn) {
1303         TSDebug(DEBUG_TAG, "[%s] Packed ESI document found in cache; will process", __FUNCTION__);
1304         retval = true;
1305       } else {
1306         TSDebug(DEBUG_TAG, "[%s] Found Intercept header in server response; document not processable", __FUNCTION__);
1307       }
1308       break; // found internal header; no other detection required
1309     }
1310 
1311     if ((!checkHeaderValue(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_TYPE, TS_MIME_LEN_CONTENT_TYPE, "text/", 5, true)) &&
1312         (!checkHeaderValue(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_TYPE, TS_MIME_LEN_CONTENT_TYPE, "application/javascript", 22,
1313                            true)) &&
1314         (!checkHeaderValue(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_TYPE, TS_MIME_LEN_CONTENT_TYPE, "application/x-javascript", 24,
1315                            true)) &&
1316         (!checkHeaderValue(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_TYPE, TS_MIME_LEN_CONTENT_TYPE, "application/json", 16, true)) &&
1317         (!checkHeaderValue(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_TYPE, TS_MIME_LEN_CONTENT_TYPE, "multipart/mixed", 15, true))) {
1318       TSDebug(DEBUG_TAG, "[%s] Not text content", __FUNCTION__);
1319       break;
1320     }
1321     if (!checkHeaderValue(bufp, hdr_loc, MIME_FIELD_XESI, MIME_FIELD_XESI_LEN)) {
1322       TSDebug(DEBUG_TAG, "[%s] ESI header [%s] not found", __FUNCTION__, MIME_FIELD_XESI);
1323       break;
1324     }
1325 
1326     retval = true;
1327   } while (false);
1328 
1329   TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
1330   return retval;
1331 }
1332 
1333 static bool
isCacheObjTransformable(TSHttpTxn txnp,bool * intercept_header,bool * head_only)1334 isCacheObjTransformable(TSHttpTxn txnp, bool *intercept_header, bool *head_only)
1335 {
1336   int obj_status;
1337   if (TSHttpTxnCacheLookupStatusGet(txnp, &obj_status) == TS_ERROR) {
1338     TSError("[esi][%s] Couldn't get cache status of object", __FUNCTION__);
1339     return false;
1340   }
1341   if (obj_status == TS_CACHE_LOOKUP_HIT_FRESH) {
1342     /*
1343     time_t respTime;
1344     if (TSHttpTxnCachedRespTimeGet(txnp, &respTime) == TS_SUCCESS) {
1345       TSError("[%s] RespTime; %d", __FUNCTION__, (int)respTime);
1346     }
1347     */
1348 
1349     TSDebug(DEBUG_TAG, "[%s] doc found in cache, will add transformation", __FUNCTION__);
1350     return isTxnTransformable(txnp, true, intercept_header, head_only);
1351   }
1352   TSDebug(DEBUG_TAG, "[%s] cache object's status is %d; not transformable", __FUNCTION__, obj_status);
1353   return false;
1354 }
1355 
1356 static bool
isInterceptRequest(TSHttpTxn txnp)1357 isInterceptRequest(TSHttpTxn txnp)
1358 {
1359   if (!TSHttpTxnIsInternal(txnp)) {
1360     TSDebug(DEBUG_TAG, "[%s] Skipping external request", __FUNCTION__);
1361     return false;
1362   }
1363 
1364   TSMBuffer bufp;
1365   TSMLoc hdr_loc;
1366   if (TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
1367     TSError("[esi][%s] Could not get client request", __FUNCTION__);
1368     return false;
1369   }
1370 
1371   bool valid_request = false;
1372   bool retval        = false;
1373   int method_len;
1374   const char *method = TSHttpHdrMethodGet(bufp, hdr_loc, &method_len);
1375   if (!method) {
1376     TSError("[esi][%s] Could not obtain method!", __FUNCTION__);
1377   } else {
1378     if ((method_len != TS_HTTP_LEN_POST) || (strncasecmp(method, TS_HTTP_METHOD_POST, TS_HTTP_LEN_POST))) {
1379       TSDebug(DEBUG_TAG, "[%s] Method [%.*s] invalid, [%s] expected", __FUNCTION__, method_len, method, TS_HTTP_METHOD_POST);
1380     } else {
1381       TSDebug(DEBUG_TAG, "[%s] Valid server intercept method found", __FUNCTION__);
1382       valid_request = true;
1383     }
1384   }
1385 
1386   if (valid_request) {
1387     retval = checkHeaderValue(bufp, hdr_loc, SERVER_INTERCEPT_HEADER, SERVER_INTERCEPT_HEADER_LEN);
1388   }
1389   TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
1390   return retval;
1391 }
1392 
1393 static bool
checkForCacheHeader(const char * name,int name_len,const char * value,int value_len,bool & cacheable)1394 checkForCacheHeader(const char *name, int name_len, const char *value, int value_len, bool &cacheable)
1395 {
1396   cacheable = true;
1397   if (Utils::areEqual(name, name_len, TS_MIME_FIELD_EXPIRES, TS_MIME_LEN_EXPIRES)) {
1398     if ((value_len == 1) && (*value == '0')) {
1399       cacheable = false;
1400     } else if (Utils::areEqual(value, value_len, "-1", 2)) {
1401       cacheable = false;
1402     }
1403     return true;
1404   }
1405   if (Utils::areEqual(name, name_len, TS_MIME_FIELD_CACHE_CONTROL, TS_MIME_LEN_CACHE_CONTROL)) {
1406     if (Utils::areEqual(value, value_len, TS_HTTP_VALUE_PRIVATE, TS_HTTP_LEN_PRIVATE)) {
1407       cacheable = false;
1408     }
1409     return true;
1410   }
1411   return false;
1412 }
1413 
1414 static bool
addSendResponseHeaderHook(TSHttpTxn txnp,const ContData * src_cont_data)1415 addSendResponseHeaderHook(TSHttpTxn txnp, const ContData *src_cont_data)
1416 {
1417   TSCont contp = TSContCreate(modifyResponseHeader, nullptr);
1418   if (!contp) {
1419     TSError("[esi][%s] Could not create continuation", __FUNCTION__);
1420     return false;
1421   }
1422   TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp);
1423   RespHdrModData *cont_data = new RespHdrModData();
1424   cont_data->option_info    = src_cont_data->option_info;
1425   cont_data->cache_txn      = src_cont_data->cache_txn;
1426   cont_data->head_only      = src_cont_data->head_only;
1427   cont_data->gzip_encoding  = src_cont_data->gzip_output;
1428   TSContDataSet(contp, cont_data);
1429   return true;
1430 }
1431 
1432 static bool
addTransform(TSHttpTxn txnp,const bool processing_os_response,const bool intercept_header,const bool head_only,const struct OptionInfo * pOptionInfo)1433 addTransform(TSHttpTxn txnp, const bool processing_os_response, const bool intercept_header, const bool head_only,
1434              const struct OptionInfo *pOptionInfo)
1435 {
1436   TSCont contp        = nullptr;
1437   ContData *cont_data = nullptr;
1438 
1439   contp = TSTransformCreate(transformHandler, txnp);
1440   if (!contp) {
1441     TSError("[esi][%s] Error while creating a new transformation", __FUNCTION__);
1442     goto lFail;
1443   }
1444 
1445   cont_data = new ContData(contp, txnp);
1446   TSContDataSet(contp, cont_data);
1447 
1448   cont_data->option_info      = pOptionInfo;
1449   cont_data->cache_txn        = !processing_os_response;
1450   cont_data->intercept_header = intercept_header;
1451   cont_data->head_only        = head_only;
1452   cont_data->getClientState();
1453   cont_data->getServerState();
1454 
1455   if (cont_data->cache_txn) {
1456     if (cont_data->option_info->packed_node_support) {
1457       if (cont_data->input_type != DATA_TYPE_PACKED_ESI) {
1458         removeCacheKey(txnp);
1459       }
1460     } else {
1461       if (cont_data->input_type == DATA_TYPE_PACKED_ESI) {
1462         removeCacheKey(txnp);
1463       }
1464     }
1465   }
1466 
1467   TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, contp);
1468 
1469   if (!addSendResponseHeaderHook(txnp, cont_data)) {
1470     TSError("[esi][%s] Couldn't add send response header hook", __FUNCTION__);
1471     goto lFail;
1472   }
1473 
1474   TSHttpTxnTransformedRespCache(txnp, 0);
1475   if (cont_data->option_info->packed_node_support) {
1476     TSHttpTxnUntransformedRespCache(txnp, 0);
1477   } else {
1478     TSHttpTxnUntransformedRespCache(txnp, 1);
1479   }
1480 
1481   TSDebug(DEBUG_TAG, "[%s] Added transformation (0x%p)", __FUNCTION__, contp);
1482   return true;
1483 
1484 lFail:
1485   if (contp) {
1486     TSContDestroy(contp);
1487   }
1488   if (cont_data) {
1489     delete cont_data;
1490   }
1491   return false;
1492 }
1493 
1494 static int
globalHookHandler(TSCont contp,TSEvent event,void * edata)1495 globalHookHandler(TSCont contp, TSEvent event, void *edata)
1496 {
1497   TSHttpTxn txnp                 = static_cast<TSHttpTxn>(edata);
1498   bool intercept_header          = false;
1499   bool head_only                 = false;
1500   bool intercept_req             = isInterceptRequest(txnp);
1501   struct OptionInfo *pOptionInfo = static_cast<struct OptionInfo *>(TSContDataGet(contp));
1502 
1503   switch (event) {
1504   case TS_EVENT_HTTP_READ_REQUEST_HDR:
1505     TSDebug(DEBUG_TAG, "[%s] handling read request header event", __FUNCTION__);
1506     if (intercept_req) {
1507       if (!setupServerIntercept(txnp)) {
1508         TSError("[esi][%s] Could not setup server intercept", __FUNCTION__);
1509       } else {
1510         TSDebug(DEBUG_TAG, "[%s] Setup server intercept", __FUNCTION__);
1511       }
1512     } else {
1513       TSDebug(DEBUG_TAG, "[%s] Not setting up intercept", __FUNCTION__);
1514     }
1515     break;
1516 
1517   case TS_EVENT_HTTP_READ_RESPONSE_HDR:
1518   case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE:
1519     if (!intercept_req) {
1520       if (event == TS_EVENT_HTTP_READ_RESPONSE_HDR) {
1521         bool mask_cache_headers = false;
1522         TSDebug(DEBUG_TAG, "[%s] handling read response header event", __FUNCTION__);
1523         if (isTxnTransformable(txnp, false, &intercept_header, &head_only)) {
1524           addTransform(txnp, true, intercept_header, head_only, pOptionInfo);
1525           Stats::increment(Stats::N_OS_DOCS);
1526           mask_cache_headers = true;
1527         }
1528         if (pOptionInfo->packed_node_support && mask_cache_headers) {
1529           // we'll 'mask' OS cache headers so that traffic server will
1530           // not try to cache this. We cannot outright delete them
1531           // because we need them in our POST request; hence the 'masking'
1532           maskOsCacheHeaders(txnp);
1533         }
1534       } else {
1535         TSDebug(DEBUG_TAG, "[%s] handling cache lookup complete event", __FUNCTION__);
1536         if (isCacheObjTransformable(txnp, &intercept_header, &head_only)) {
1537           // we make the assumption above that a transformable cache
1538           // object would already have a transformation. We should revisit
1539           // that assumption in case we change the statement below
1540           addTransform(txnp, false, intercept_header, head_only, pOptionInfo);
1541           Stats::increment(Stats::N_CACHE_DOCS);
1542         }
1543       }
1544     }
1545     break;
1546 
1547   default:
1548     TSDebug(DEBUG_TAG, "[%s] Don't know how to handle event type %d", __FUNCTION__, event);
1549     break;
1550   }
1551 
1552   TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
1553   return 0;
1554 }
1555 
1556 static void
loadHandlerConf(const char * file_name,Utils::KeyValueMap & handler_conf)1557 loadHandlerConf(const char *file_name, Utils::KeyValueMap &handler_conf)
1558 {
1559   std::list<string> conf_lines;
1560   TSFile conf_file = TSfopen(file_name, "r");
1561   if (conf_file != nullptr) {
1562     char buf[1024];
1563     while (TSfgets(conf_file, buf, sizeof(buf) - 1) != nullptr) {
1564       conf_lines.push_back(string(buf));
1565     }
1566     TSfclose(conf_file);
1567     Utils::parseKeyValueConfig(conf_lines, handler_conf, gWhitelistCookies);
1568     TSDebug(DEBUG_TAG, "[%s] Loaded handler conf file [%s]", __FUNCTION__, file_name);
1569   } else {
1570     TSError("[esi][%s] Failed to open handler config file [%s]", __FUNCTION__, file_name);
1571   }
1572 }
1573 
1574 static int
esiPluginInit(int argc,const char * argv[],struct OptionInfo * pOptionInfo)1575 esiPluginInit(int argc, const char *argv[], struct OptionInfo *pOptionInfo)
1576 {
1577   static TSStatSystem *statSystem = nullptr;
1578 
1579   if (statSystem == nullptr) {
1580     statSystem = new TSStatSystem();
1581     Utils::init(&TSDebug, &TSError);
1582     Stats::init(statSystem);
1583   }
1584 
1585   if (gHandlerManager == nullptr) {
1586     gHandlerManager = new HandlerManager(HANDLER_MGR_DEBUG_TAG, &TSDebug, &TSError);
1587   }
1588 
1589   memset(pOptionInfo, 0, sizeof(struct OptionInfo));
1590 
1591   if (argc > 1) {
1592     int c;
1593     static const struct option longopts[] = {
1594       {const_cast<char *>("packed-node-support"), no_argument, nullptr, 'n'},
1595       {const_cast<char *>("private-response"), no_argument, nullptr, 'p'},
1596       {const_cast<char *>("disable-gzip-output"), no_argument, nullptr, 'z'},
1597       {const_cast<char *>("first-byte-flush"), no_argument, nullptr, 'b'},
1598       {const_cast<char *>("handler-filename"), required_argument, nullptr, 'f'},
1599       {nullptr, 0, nullptr, 0},
1600     };
1601 
1602     int longindex = 0;
1603     while ((c = getopt_long(argc, const_cast<char *const *>(argv), "npzbf:", longopts, &longindex)) != -1) {
1604       switch (c) {
1605       case 'n':
1606         pOptionInfo->packed_node_support = true;
1607         break;
1608       case 'p':
1609         pOptionInfo->private_response = true;
1610         break;
1611       case 'z':
1612         pOptionInfo->disable_gzip_output = true;
1613         break;
1614       case 'b':
1615         pOptionInfo->first_byte_flush = true;
1616         break;
1617       case 'f': {
1618         Utils::KeyValueMap handler_conf;
1619         loadHandlerConf(optarg, handler_conf);
1620         gHandlerManager->loadObjects(handler_conf);
1621         break;
1622       }
1623       default:
1624         break;
1625       }
1626     }
1627   }
1628 
1629   TSDebug(DEBUG_TAG,
1630           "[%s] Plugin started, "
1631           "packed-node-support: %d, private-response: %d, "
1632           "disable-gzip-output: %d, first-byte-flush: %d ",
1633           __FUNCTION__, pOptionInfo->packed_node_support, pOptionInfo->private_response, pOptionInfo->disable_gzip_output,
1634           pOptionInfo->first_byte_flush);
1635 
1636   return 0;
1637 }
1638 
1639 void
TSPluginInit(int argc,const char * argv[])1640 TSPluginInit(int argc, const char *argv[])
1641 {
1642   TSPluginRegistrationInfo info;
1643   info.plugin_name   = (char *)"esi";
1644   info.vendor_name   = (char *)"Apache Software Foundation";
1645   info.support_email = (char *)"dev@trafficserver.apache.org";
1646 
1647   if (TSPluginRegister(&info) != TS_SUCCESS) {
1648     TSError("[esi][%s] plugin registration failed", __FUNCTION__);
1649     return;
1650   }
1651 
1652   struct OptionInfo *pOptionInfo = static_cast<struct OptionInfo *>(TSmalloc(sizeof(struct OptionInfo)));
1653   if (pOptionInfo == nullptr) {
1654     TSError("[esi][%s] malloc %d bytes fail", __FUNCTION__, static_cast<int>(sizeof(struct OptionInfo)));
1655     return;
1656   }
1657   if (esiPluginInit(argc, argv, pOptionInfo) != 0) {
1658     TSfree(pOptionInfo);
1659     return;
1660   }
1661 
1662   TSCont global_contp = TSContCreate(globalHookHandler, nullptr);
1663   if (!global_contp) {
1664     TSError("[esi][%s] Could not create global continuation", __FUNCTION__);
1665     TSfree(pOptionInfo);
1666     return;
1667   }
1668   TSContDataSet(global_contp, pOptionInfo);
1669 
1670   TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, global_contp);
1671   TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, global_contp);
1672   TSHttpHookAdd(TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, global_contp);
1673 }
1674 
1675 ///////////////////////////////////////////////////////////////////////////////
1676 // Initialize the plugin as a remap plugin.
1677 //
1678 TSReturnCode
TSRemapInit(TSRemapInterface * api_info,char * errbuf,int errbuf_size)1679 TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
1680 {
1681   if (!api_info) {
1682     snprintf(errbuf, errbuf_size, "[TSRemapInit] - Invalid TSRemapInterface argument");
1683     TSError("[esi][TSRemapInit] - Invalid TSRemapInterface argument");
1684     return TS_ERROR;
1685   }
1686 
1687   if (api_info->size < sizeof(TSRemapInterface)) {
1688     snprintf(errbuf, errbuf_size, "[TSRemapInit] - Incorrect size of TSRemapInterface structure");
1689     TSError("[esi][TSRemapInit] - Incorrect size of TSRemapInterface structure");
1690     return TS_ERROR;
1691   }
1692 
1693   TSDebug(DEBUG_TAG, "esi remap plugin is successfully initialized");
1694   return TS_SUCCESS;
1695 }
1696 
1697 TSReturnCode
TSRemapNewInstance(int argc,char * argv[],void ** ih,char * errbuf,int errbuf_size)1698 TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuf, int errbuf_size)
1699 {
1700   if (argc < 2) {
1701     snprintf(errbuf, errbuf_size,
1702              "Unable to create remap instance, "
1703              "argc: %d < 2",
1704              argc);
1705     TSError("[esi]Unable to create remap instance! argc: %d < 2", argc);
1706     return TS_ERROR;
1707   }
1708 
1709   int index = 0;
1710   const char *new_argv[argc];
1711 
1712   new_argv[index++] = "esi.so";
1713   for (int i = 2; i < argc; i++) {
1714     new_argv[index++] = argv[i];
1715   }
1716   new_argv[index] = nullptr;
1717 
1718   struct OptionInfo *pOptionInfo = static_cast<struct OptionInfo *>(TSmalloc(sizeof(struct OptionInfo)));
1719   if (pOptionInfo == nullptr) {
1720     snprintf(errbuf, errbuf_size, "malloc %d bytes fail", static_cast<int>(sizeof(struct OptionInfo)));
1721     TSError("[esi][%s] malloc %d bytes fail", __FUNCTION__, static_cast<int>(sizeof(struct OptionInfo)));
1722     return TS_ERROR;
1723   }
1724   if (esiPluginInit(index, new_argv, pOptionInfo) != 0) {
1725     snprintf(errbuf, errbuf_size, "esiPluginInit fail!");
1726     TSfree(pOptionInfo);
1727     return TS_ERROR;
1728   }
1729   TSCont contp = TSContCreate(globalHookHandler, nullptr);
1730   TSContDataSet(contp, pOptionInfo);
1731   *ih = static_cast<void *>(contp);
1732 
1733   return TS_SUCCESS;
1734 }
1735 
1736 void
TSRemapDeleteInstance(void * ih)1737 TSRemapDeleteInstance(void *ih)
1738 {
1739   TSCont contp = static_cast<TSCont>(ih);
1740   if (contp != nullptr) {
1741     TSContDestroy(contp);
1742   }
1743 }
1744 
1745 ///////////////////////////////////////////////////////////////////////////////
1746 // Main entry point when used as a remap plugin.
1747 //
1748 TSRemapStatus
TSRemapDoRemap(void * ih,TSHttpTxn txnp,TSRemapRequestInfo *)1749 TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo * /* rri ATS_UNUSED */)
1750 {
1751   if (nullptr != ih) {
1752     TSCont contp = static_cast<TSCont>(ih);
1753     TSHttpTxnHookAdd(txnp, TS_HTTP_READ_RESPONSE_HDR_HOOK, contp);
1754     TSHttpTxnHookAdd(txnp, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, contp);
1755 
1756     if (isInterceptRequest(txnp)) {
1757       if (!setupServerIntercept(txnp)) {
1758         TSError("[esi][%s] Could not setup server intercept", __FUNCTION__);
1759       } else {
1760         TSDebug(DEBUG_TAG, "[%s] Setup server intercept", __FUNCTION__);
1761       }
1762     } else {
1763       TSDebug(DEBUG_TAG, "[%s] Not setting up intercept", __FUNCTION__);
1764     }
1765   }
1766 
1767   return TSREMAP_NO_REMAP; // This plugin never rewrites anything.
1768 }
1769