File: icap_plugin.cc

package info (click to toggle)
trafficserver 9.2.5%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 53,008 kB
  • sloc: cpp: 345,484; ansic: 31,134; python: 24,200; sh: 7,271; makefile: 3,045; perl: 2,261; java: 277; pascal: 119; sql: 94; xml: 2
file content (1248 lines) | stat: -rw-r--r-- 39,502 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
/** @file

  @brief A plugin that sends the http body through an ICAP request to a
         scanner server. If malicious content is detected, then the scanner
         will return an error message body, which we will pass to the user
         agent. Otherwise it will return the same content that was passed to
         it, in which case we will pass the content to user agent.

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

#include <string>
#include <cstring>
#include <regex>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <cinttypes>
#include <iostream>
#include <sstream>

#include "ts/ts.h"

#define PLUGIN_NAME "icap_plugin"

enum class State {
  BEGIN = 1,
  CONNECT,
  WRITE_HEADER,
  WRITE_BODY,
  READ_ICAP_HEADER,
  READ_HTTP_HEADER,
  READ_HTTP_BODY,
  SEND_ERROR_MSG,
  BYPASS,
  BUFFER_OS_RESP,
  SEND_OS_RESP
};

#define ICAP_SERVICE_URL "icap://127.0.0.1/avscan"
#define ICAP_VERSION "1.0"

struct TransformData {
  State state = State::BEGIN;
  const TSHttpTxn txn;

  int64_t server_reply_content_length;

  TSIOBuffer input_buf          = nullptr;
  TSIOBufferReader input_reader = nullptr;

  TSIOBuffer os_resp_buf          = nullptr;
  TSIOBufferReader os_resp_reader = nullptr;

  int64_t done_write = false;

  TSIOBuffer icap_resp_buf          = nullptr;
  TSIOBufferReader icap_resp_reader = nullptr;

  TSIOBuffer output_buf          = nullptr;
  TSIOBufferReader output_reader = nullptr;
  TSVConn output_vc              = nullptr;
  TSVIO output_vio               = nullptr;

  TSAction pending_action = nullptr;
  TSVConn icap_vc         = nullptr;
  TSVIO icap_vio          = nullptr;

  std::string icap_header;
  std::string http_header;
  std::string chunk_length_str;
  int64_t icap_reply_content_length = 0;

  int64_t http_body_chunk_length         = -1;
  int64_t http_body_total_length_written = 0;

  bool eos_detected = false;

  std::string err_msg;

  TransformData(TSHttpTxn txnp);
  ~TransformData();
};

/* Configurable parameters */
static std::string server_ip;
static int server_port;
static int carp_port;
static int debug_enabled;

/* Stats for debug */
static int scan_passed;
static int scan_failed;
static int icap_conn_failed;
static int total_icap_invalid;
static int icap_response_err;
static int icap_write_failed;

static int transform_handler(TSCont contp, TSEvent event, void *edata);
static int transform_read_http_header_event(TSCont contp, TransformData *data, TSEvent event, void *edata);
static int transform_send_error_msg(TSCont contp, TransformData *data);
static int transform_bypass(TSCont contp, TransformData *data);
static int transform_send_os_resp(TSCont contp, TransformData *data);

TransformData::TransformData(TSHttpTxn txnp) : txn(txnp) {}

TransformData::~TransformData()
{
  if (icap_vc) {
    TSVConnAbort(icap_vc, 1);
  }
  if (input_reader) {
    TSIOBufferReaderFree(input_reader);
  }
  if (input_buf) {
    TSIOBufferDestroy(input_buf);
  }
  if (os_resp_reader) {
    TSIOBufferReaderFree(os_resp_reader);
  }
  if (os_resp_buf) {
    TSIOBufferDestroy(os_resp_buf);
  }
  if (icap_resp_reader) {
    TSIOBufferReaderFree(icap_resp_reader);
  }
  if (icap_resp_buf) {
    TSIOBufferDestroy(icap_resp_buf);
  }
  if (output_reader) {
    TSIOBufferReaderFree(output_reader);
  }
  if (output_buf) {
    TSIOBufferDestroy(output_buf);
  }
  if (pending_action) {
    TSActionCancel(pending_action);
  }
}

/*
 * get_port
 * Description: Return the port of a sockaddr
 */
uint16_t
get_port(sockaddr const *s_sockaddr)
{
  switch (s_sockaddr->sa_family) {
  case AF_INET: {
    const struct sockaddr_in *s_sockaddr_in = reinterpret_cast<const struct sockaddr_in *>(s_sockaddr);
    return ntohs(s_sockaddr_in->sin_port);
  } break;
  case AF_INET6: {
    const struct sockaddr_in6 *s_sockaddr_in6 = reinterpret_cast<const struct sockaddr_in6 *>(s_sockaddr);
    return ntohs(s_sockaddr_in6->sin6_port);
  } break;
  default:
    return 0;
    break;
  }
}

/*
 * setup_icap_status_header (Used only in debug-mode)
 * Description: This function is called to add a customized header
 *              indicating ICAP server status for logging.
 */
static void
setup_icap_status_header(TransformData *data, const char *header, const char *value)
{
  TSMBuffer bufp;
  TSMLoc resp_loc, field_loc;

  if (TSHttpTxnTransformRespGet(data->txn, &bufp, &resp_loc) != TS_SUCCESS) {
    TSError("[%s] Couldn't retrieve transform response header", PLUGIN_NAME);
    return;
  }

  if (TSMimeHdrFieldCreate(bufp, resp_loc, &field_loc) != TS_SUCCESS) {
    TSError("[%s] Unable to create field", PLUGIN_NAME);
    TSHandleMLocRelease(bufp, TS_NULL_MLOC, resp_loc);
    return;
  }

  TSMimeHdrFieldNameSet(bufp, resp_loc, field_loc, header, strlen(header));
  TSMimeHdrFieldValueStringInsert(bufp, resp_loc, field_loc, 0, value, strlen(value));
  TSMimeHdrFieldAppend(bufp, resp_loc, field_loc);

  TSHandleMLocRelease(bufp, resp_loc, field_loc);
  TSHandleMLocRelease(bufp, TS_NULL_MLOC, resp_loc);
}

/*
 * handle_invalid_icap_behavior
 * Description: This function is called when abnormal behavior of ICAP server,
 *              for instance, unsuccessful connection, is detected.
 */
static int
handle_invalid_icap_behavior(TSCont contp, TransformData *data, const char *msg)
{
  if (data->icap_vc) {
    TSVConnAbort(data->icap_vc, 1);
    data->icap_vc  = nullptr;
    data->icap_vio = nullptr;
  }
  TSStatIntIncrement(total_icap_invalid, 1);
  TSDebug(PLUGIN_NAME, "\n%s\n", data->icap_header.c_str());
  data->err_msg = std::string(msg);
  /* Signal the upstream vconn if still exists to stop sending data */
  TSVIO write_vio = TSVConnWriteVIOGet(contp);
  if (TSVIOBufferGet(write_vio)) {
    TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_COMPLETE, write_vio);
  }

  TSMBuffer bufp;
  TSMLoc hdr_loc;

  if (TSHttpTxnTransformRespGet(data->txn, &bufp, &hdr_loc) != TS_SUCCESS) {
    TSError("[%s] Couldn't retrieve transform response header", PLUGIN_NAME);
    return 0;
  }
  /* Clear all headers from the transform response */
  if (TSMimeHdrFieldsClear(bufp, hdr_loc) == TS_ERROR) {
    TSError("[%s] Couldn't clear client response header", PLUGIN_NAME);
    return 0;
  }
  TSHttpHdrStatusSet(bufp, hdr_loc, TS_HTTP_STATUS_BAD_GATEWAY);
  TSHttpHdrReasonSet(bufp, hdr_loc, TSHttpHdrReasonLookup(TS_HTTP_STATUS_BAD_GATEWAY),
                     strlen(TSHttpHdrReasonLookup(TS_HTTP_STATUS_BAD_GATEWAY)));
  TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
  transform_send_error_msg(contp, data);

  return 0;
}

/*
 * handle_icap_headers
 * Description: This is a good place to determine what to do next based on
 *              icap eaders from response of icap server.
 */
static int
handle_icap_headers(TSCont contp, TransformData *data)
{
  int64_t pos = data->icap_header.find("\r\n");
  std::string icap_status_line =
    pos != static_cast<int64_t>(std::string::npos) ? data->icap_header.substr(0, pos) : data->icap_header;
  /* Check icap header to determine whether the scan passed or not */
  if (data->icap_header.find("X-Infection-Found") != std::string::npos ||
      data->icap_header.find("X-Violations-Found") != std::string::npos) {
    TSStatIntIncrement(scan_failed, 1);
  } else {
    TSStatIntIncrement(scan_passed, 1);
  }
  /* If debug-mode is enabled, add header to log ICAP status */
  if (debug_enabled) {
    if (icap_status_line.find("506") != std::string::npos) {
      setup_icap_status_header(data, "@ICAP-Status", "ICAP server is too busy");
      TSDebug(PLUGIN_NAME, "Sending OS response body.");
      return 1;
    }
  }

  return 0;
}

/*
 * handle_icap_http_header
 * Description: This is a good place to determine what to do next based on
 *              modified http headers from response of icap server.
 */
static void
handle_icap_http_header(TransformData *data)
{
  // TSDebug(PLUGIN_NAME, "Handling http header");
  int64_t pos = data->http_header.find("\r\n");
  std::string http_status_line =
    pos != static_cast<int64_t>(std::string::npos) ? data->http_header.substr(0, pos) : data->http_header;
  /* find content length from header if any */
  std::smatch sm;
  std::regex e("(Content-Length: )([[:digit:]]+)");
  regex_search(data->http_header, sm, e);
  if (sm.size()) {
    data->icap_reply_content_length = std::stoll(sm[2].str().c_str(), nullptr, 10);
  }
  /* Replace header with the returned header from icap server */
  TSMBuffer bufp;
  TSMLoc hdr_loc;
  TSHttpParser parser;
  const char *raw_resp = data->http_header.c_str();

  if (TSHttpTxnTransformRespGet(data->txn, &bufp, &hdr_loc) != TS_SUCCESS) {
    TSError("[%s] Couldn't retrieve transform response header", PLUGIN_NAME);
    return;
  }
  /* Clear all headers from the transform response */
  if (TSMimeHdrFieldsClear(bufp, hdr_loc) == TS_ERROR) {
    TSError("[%s] Couldn't clear client response header", PLUGIN_NAME);
    return;
  }
  /* Create the new header using http header in icap response */
  parser = TSHttpParserCreate();
  TSHttpHdrParseResp(parser, bufp, hdr_loc, &raw_resp, raw_resp + data->http_header.size());

  TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
}

/*
 * handle_read_http_body
 * Description: This function handles reading http body from icap server, as
 *              well as writing body to downstream. It should be called whenever
 *              new data is available to read or WRITE_READY is received from
 *              downstream.
 */
static int
handle_read_http_body(TSCont contp, TransformData *data)
{
  int64_t avail = TSIOBufferReaderAvail(data->icap_resp_reader);

  if (avail > 0) {
    /* Read the chunk length if one is not available */
    if (data->http_body_chunk_length <= 0) {
      int64_t data_len;
      const char *buf;
      int64_t consumed    = data->chunk_length_str.size();
      TSIOBufferBlock blk = TSIOBufferReaderStart(data->icap_resp_reader);

      while (blk != nullptr) {
        buf               = TSIOBufferBlockReadStart(blk, data->icap_resp_reader, &data_len);
        std::string chunk = std::string(buf, data_len);
        /* keep the read string in body_length in case complete data haven't arrived */
        data->chunk_length_str += chunk;
        /* Look for end of reply token */
        if (data->chunk_length_str.find("\r\n0\r\n\r\n") != std::string::npos) {
          TSVIONBytesSet(data->output_vio, data->http_body_total_length_written);
          return 0;
        }
        /* TODO replace this regex with more direct (and cheaper) parsing */
        /* Look for hex string indicating chunk length */
        std::smatch sm;
        std::regex e("(\r\n)([[:xdigit:]]+)(\r\n)");
        regex_search(data->chunk_length_str, sm, e);
        /* A match means we have finished reading the length */
        if (sm.size()) {
          int64_t pos          = sm.position(0);
          int64_t token_length = sm[0].length();

          data->http_body_chunk_length = std::stoi(sm[2].str().c_str(), nullptr, 16);
          data->http_body_total_length_written += data->http_body_chunk_length;
          TSIOBufferReaderConsume(data->icap_resp_reader, pos + token_length - consumed);
          break;
        }

        TSIOBufferReaderConsume(data->icap_resp_reader, data_len);
        consumed += data_len;
        blk = TSIOBufferBlockNext(blk);
      }
      if (blk == nullptr) {
        return 0;
      }
    }

    /* Write the chunk to downstream */
    int64_t towrite;

    avail   = TSIOBufferReaderAvail(data->icap_resp_reader);
    towrite = data->http_body_chunk_length < avail ? data->http_body_chunk_length : avail;
    data->http_body_chunk_length -= towrite;
    TSIOBufferCopy(TSVIOBufferGet(data->output_vio), data->icap_resp_reader, towrite, 0);
    TSIOBufferReaderConsume(data->icap_resp_reader, towrite);

    if (data->http_body_chunk_length <= 0) {
      data->chunk_length_str.clear();
      return 0;
    }
  } else {
    /* If no more data is to be read for now. Check for eos to determine whether data is incomplete. */
    if (data->eos_detected) {
      TSVConnAbort(data->icap_vc, 1);
      data->icap_vc  = nullptr;
      data->icap_vio = nullptr;

      TSVConnAbort(data->output_vc, 1);
      data->output_vc  = nullptr;
      data->output_vio = nullptr;
      return 0;
    }
  }

  return 0;
}

static TSCont
transform_create(TSHttpTxn txnp)
{
  TSCont contp;
  TransformData *data;

  contp = TSTransformCreate(transform_handler, txnp);
  data  = new TransformData(txnp);

  TSContDataSet(contp, data);
  // TSDebug(PLUGIN_NAME, "Initialization complete.");
  return contp;
}

static void
transform_destroy(TSCont contp)
{
  TransformData *data;

  data = static_cast<TransformData *>(TSContDataGet(contp));

  if (data != nullptr) {
    delete data;
  } else {
    TSError("[%s] Unable to get Continuation's Data. TSContDataGet returns NULL", PLUGIN_NAME);
  }

  TSContDestroy(contp);
}

/*
 * transform_connect
 * Description: Issue a socket connection to icap server.
 */
static int
transform_connect(TSCont contp, TransformData *data)
{
  TSAction action;
  struct sockaddr_in ip_addr;
  data->state = State::CONNECT;

  /* Only support IPv4 at this point */
  memset(&ip_addr, 0, sizeof(ip_addr));
  ip_addr.sin_family = AF_INET;
  ip_addr.sin_port   = htons(server_port);
  if (inet_pton(AF_INET, server_ip.c_str(), &ip_addr.sin_addr) <= 0) {
    TSError("[%s] Invalid address: %s", PLUGIN_NAME, server_ip.c_str());
    return 0;
  }
  action = TSNetConnect(contp, reinterpret_cast<struct sockaddr const *>(&ip_addr));

  if (!TSActionDone(action)) {
    data->pending_action = action;
  }

  return 0;
}

static int
transform_write_body(TSCont contp, TransformData *data)
{
  data->state = State::WRITE_BODY;
  /* If debug-mode enabled, allocate buffer to store origin response */
  if (debug_enabled) {
    data->os_resp_buf    = TSIOBufferCreate();
    data->os_resp_reader = TSIOBufferReaderAlloc(data->os_resp_buf);
  }
  return 0;
}

static int
transform_read_icap_header(TSCont contp, TransformData *data)
{
  data->state = State::READ_ICAP_HEADER;

  data->icap_resp_buf    = TSIOBufferCreate();
  data->icap_resp_reader = TSIOBufferReaderAlloc(data->icap_resp_buf);

  if (data->icap_resp_reader != nullptr) {
    data->icap_vio = TSVConnRead(data->icap_vc, contp, data->icap_resp_buf, INT64_MAX);
  } else {
    TSError("[%s] Error in Allocating a Reader to output buffer. TSIOBufferReaderAlloc returns NULL", PLUGIN_NAME);
  }

  return 0;
}

static int
transform_read_http_header(TSCont contp, TransformData *data)
{
  data->state = State::READ_HTTP_HEADER;

  if (TSIOBufferReaderAvail(data->icap_resp_reader)) {
    transform_read_http_header_event(contp, data, TS_EVENT_VCONN_READ_READY, nullptr);
  }

  return 0;
}

static int
transform_read_http_body(TSCont contp, TransformData *data)
{
  data->state = State::READ_HTTP_BODY;

  data->output_buf    = TSIOBufferCreate();
  data->output_reader = TSIOBufferReaderAlloc(data->output_buf);
  data->output_vc     = TSTransformOutputVConnGet(static_cast<TSVConn>(contp));
  // data->output_vio = TSVConnWrite(data->output_vc, contp, data->output_reader, INT64_MAX);
  if (data->icap_reply_content_length) {
    data->output_vio = TSVConnWrite(data->output_vc, contp, data->output_reader, data->icap_reply_content_length);
  } else {
    data->output_vio = TSVConnWrite(data->output_vc, contp, data->output_reader, INT64_MAX);
  }

  if (TSIOBufferReaderAvail(data->icap_resp_reader)) {
    return handle_read_http_body(contp, data);
  }

  return 0;
}

static int
handle_write_header(TSCont contp, TransformData *data)
{
  data->state        = State::WRITE_HEADER;
  data->input_buf    = TSIOBufferCreate();
  data->input_reader = TSIOBufferReaderAlloc(data->input_buf);
  data->icap_vio     = TSVConnWrite(data->icap_vc, contp, data->input_reader, INT64_MAX);

  /* Acquire client request and server response header */
  TSMBuffer bufp_c, bufp_s;
  TSMLoc req_loc, resp_loc;

  if (TSHttpTxnClientReqGet(data->txn, &bufp_c, &req_loc) != TS_SUCCESS) {
    TSError("[%s] Couldn't retrieve client request header", PLUGIN_NAME);
    return 0;
  }

  if (TSHttpTxnServerRespGet(data->txn, &bufp_s, &resp_loc) != TS_SUCCESS) {
    TSError("[%s] Couldn't retrieve server response header", PLUGIN_NAME);
    TSHandleMLocRelease(bufp_c, TS_NULL_MLOC, req_loc);
    return 0;
  }

  int64_t client_req_size  = TSHttpHdrLengthGet(bufp_c, req_loc);
  int64_t server_resp_size = TSHttpHdrLengthGet(bufp_s, resp_loc);
  /* formulate the ICAP request header */
  char res_buf[1000];
  memset(res_buf, 0, 1000);
  sprintf(res_buf,
          "RESPMOD %s ICAP/%s\r\n"
          "Host: %s\r\n"
          "Connection: close\r\n" // "Connection: close" is used since each scan creates a new connection
          "Encapsulated: req-hdr=0, res-hdr=%" PRIu64 ", res-body=%" PRIu64 "\r\n\r\n",
          ICAP_SERVICE_URL, ICAP_VERSION, server_ip.c_str(), client_req_size, server_resp_size + client_req_size);

  TSIOBufferWrite(data->input_buf, (const char *)res_buf, strlen(res_buf));
  TSHttpHdrPrint(bufp_c, req_loc, data->input_buf);
  TSHttpHdrPrint(bufp_s, resp_loc, data->input_buf);
  data->done_write += TSIOBufferReaderAvail(data->input_reader);

  TSHandleMLocRelease(bufp_c, TS_NULL_MLOC, req_loc);
  TSHandleMLocRelease(bufp_s, TS_NULL_MLOC, resp_loc);

  return transform_write_body(contp, data);
}

static int
handle_write_body(TSCont contp, TransformData *data)
{
  TSVIO write_vio;
  int64_t towrite;
  char *end_of_request = (char *)"\r\n0; ieof\r\n\r\n";

  write_vio = TSVConnWriteVIOGet(contp);
  /* check if the write VIO's buffer is non-NULL. */
  if (!TSVIOBufferGet(write_vio)) {
    /* Check if there is no body to scan. Skip scanning if no body */
    if (!data->server_reply_content_length) {
      TSVIONBytesSet(data->icap_vio, 0);
      if (TSVIOBufferGet(write_vio)) {
        TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_COMPLETE, write_vio);
      }
      return transform_bypass(contp, data);
    }
    TSIOBufferWrite(TSVIOBufferGet(data->icap_vio), (const char *)end_of_request, strlen(end_of_request));
    data->done_write += strlen(end_of_request);
    TSVIONBytesSet(data->icap_vio, data->done_write);
    TSVIOReenable(data->icap_vio);
    return 0;
  }

  /* Determine how much data we have left to read. */
  towrite = TSVIONTodoGet(write_vio);

  if (towrite > 0) {
    /* The amount of data left to read needs to be truncated by
       the amount of data actually in the read buffer. */
    int64_t avail = TSIOBufferReaderAvail(TSVIOReaderGet(write_vio));
    if (towrite > avail) {
      towrite = avail;
    }
    if (towrite > 0) {
      /* Copy the data from the read buffer to the input buffer. */
      std::stringstream ss;
      ss << std::hex << towrite;
      std::string chunk_size            = data->server_reply_content_length <= 0 ? ss.str() + "\r\n" : "\r\n" + ss.str() + "\r\n";
      data->server_reply_content_length = towrite;
      TSIOBufferWrite(TSVIOBufferGet(data->icap_vio), chunk_size.c_str(), chunk_size.size());
      data->done_write += chunk_size.size();
      TSIOBufferCopy(TSVIOBufferGet(data->icap_vio), TSVIOReaderGet(write_vio), towrite, 0);
      if (debug_enabled) {
        /* If debug-mode enabled, buffer origin response */
        TSIOBufferCopy(data->os_resp_buf, TSVIOReaderGet(write_vio), towrite, 0);
      }
      /* Tell the read buffer that we have read the data and are no
         longer interested in it. */
      TSIOBufferReaderConsume(TSVIOReaderGet(write_vio), towrite);
      /* Modify the write VIO to reflect how much data we've
         completed. */
      TSVIONDoneSet(write_vio, TSVIONDoneGet(write_vio) + towrite);
      data->done_write += towrite;
    }
  }

  /* Now we check the write VIO to see if there is data left to
     read. */
  if (TSVIONTodoGet(write_vio) > 0) {
    /* Call back the write VIO continuation to let it know that we
       are ready for more data. */
    TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_READY, write_vio);
  } else {
    TSIOBufferWrite(TSVIOBufferGet(data->icap_vio), (const char *)end_of_request, strlen(end_of_request));
    data->done_write += strlen(end_of_request);
    TSVIONBytesSet(data->icap_vio, data->done_write);
    TSVIOReenable(data->icap_vio);
    /* Call back the write VIO continuation to let it know that we
       have completed the write operation. */
    TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_COMPLETE, write_vio);

    return 0;
  }

  return 0;
}

/*
 * transform_send_error_msg
 * Description: Send the error message to user agent
 */
static int
transform_send_error_msg(TSCont contp, TransformData *data)
{
  data->state         = State::SEND_ERROR_MSG;
  data->output_buf    = TSIOBufferCreate();
  data->output_reader = TSIOBufferReaderAlloc(data->output_buf);
  data->output_vc     = TSTransformOutputVConnGet(static_cast<TSVConn>(contp));

  TSIOBufferWrite(data->output_buf, data->err_msg.c_str(), data->err_msg.size());

  if (data->output_vc == nullptr) {
    TSError("[%s] TSTransformOutputVConnGet returns NULL", PLUGIN_NAME);
  } else {
    data->output_vio = TSVConnWrite(data->output_vc, contp, data->output_reader, TSIOBufferReaderAvail(data->output_reader));
    if (data->output_vio == nullptr) {
      TSError("[%s] TSVConnWrite returns NULL", PLUGIN_NAME);
    }
  }
  return 1;
}

/*
 * transform_bypass
 * Description: In the case there is no body to transform, bypass scan and
 *              initiate a write of 0 bytes to downstream.
 */
static int
transform_bypass(TSCont contp, TransformData *data)
{
  data->state         = State::BYPASS;
  data->output_buf    = TSIOBufferCreate();
  data->output_reader = TSIOBufferReaderAlloc(data->output_buf);
  data->output_vc     = TSTransformOutputVConnGet(static_cast<TSVConn>(contp));

  if (data->output_vc == nullptr) {
    TSError("[%s] TSTransformOutputVConnGet returns NULL", PLUGIN_NAME);
  } else {
    data->output_vio = TSVConnWrite(data->output_vc, contp, data->output_reader, 0);
    if (data->output_vio == nullptr) {
      TSError("[%s] TSVConnWrite returns NULL", PLUGIN_NAME);
    }
  }

  return 0;
}

/*
 * transform_buffer_os_resp (Used only in debug-mode)
 * Description: Buffer response body from origin server.
 */
static int
transform_buffer_os_resp(TSCont contp, TransformData *data)
{
  data->state = State::BUFFER_OS_RESP;
  TSDebug(PLUGIN_NAME, "Buffer os response.");
  if (!data->os_resp_buf) {
    data->os_resp_buf = TSIOBufferCreate();
  }

  if (!data->os_resp_reader) {
    data->os_resp_reader = TSIOBufferReaderAlloc(data->os_resp_buf);
  }

  return 0;
}

/*
 * transform_send_os_resp (Used only in debug-mode)
 * Description: Send buffered response body from origin to
 *              user-agent without scanning.
 */
static int
transform_send_os_resp(TSCont contp, TransformData *data)
{
  data->state         = State::SEND_OS_RESP;
  data->output_buf    = TSIOBufferCreate();
  data->output_reader = TSIOBufferReaderAlloc(data->output_buf);
  data->output_vc     = TSTransformOutputVConnGet(static_cast<TSVConn>(contp));

  if (data->output_vc == nullptr) {
    TSError("[%s] TSTransformOutputVConnGet returns NULL", PLUGIN_NAME);
  } else {
    data->output_vio = TSVConnWrite(data->output_vc, contp, data->os_resp_reader, TSIOBufferReaderAvail(data->os_resp_reader));
    if (data->output_vio == nullptr) {
      TSError("[%s] TSVConnWrite returns NULL", PLUGIN_NAME);
    }
  }

  return 0;
}

static int
transform_connect_event(TSCont contp, TransformData *data, TSEvent event, void *edata)
{
  switch (event) {
  case TS_EVENT_NET_CONNECT:
    data->pending_action = nullptr;
    data->icap_vc        = static_cast<TSVConn>(edata);
    return handle_write_header(contp, data);
  case TS_EVENT_NET_CONNECT_FAILED:
    TSStatIntIncrement(icap_conn_failed, 1);
    data->pending_action = nullptr;
    return handle_invalid_icap_behavior(contp, data, "Cannot connect to ICAP scanner.");
  default:
    break;
  }

  return 0;
}

static int
transform_write_header_event(TSCont contp, TransformData *data, TSEvent event, void *edata)
{
  switch (event) {
  case TS_EVENT_VCONN_WRITE_COMPLETE:
    return transform_write_body(contp, data);
  case TS_EVENT_ERROR:
    return handle_invalid_icap_behavior(contp, data, "Error writing header to ICAP scanner");
  case TS_EVENT_IMMEDIATE:
    TSVIOReenable(data->icap_vio);
  default:
    break;
  }

  return 0;
}

static int
transform_write_event(TSCont contp, TransformData *data, TSEvent event, void *edata)
{
  switch (event) {
  case TS_EVENT_VCONN_WRITE_COMPLETE:
    return transform_read_icap_header(contp, data);
  case TS_EVENT_ERROR:
    TSStatIntIncrement(icap_write_failed, 1);
    /* In case of not able to write to icap, if debug-mode enabled,
     * setup header to log icap status and proceed to buffer origin
     * response to return to user. If not enabled,  return HTTP 502
     * to client.
     */
    if (debug_enabled) {
      setup_icap_status_header(data, "@ICAP-Status", "Cannot connect to ICAP server");
      return transform_buffer_os_resp(contp, data);
    } else {
      return handle_invalid_icap_behavior(contp, data, "Error writing body to ICAP scanner");
    }
  case TS_EVENT_VCONN_WRITE_READY:
  default:
    return handle_write_body(contp, data);
  }

  return 0;
}

static int
transform_read_icap_header_event(TSCont contp, TransformData *data, TSEvent event, void *edata)
{
  switch (event) {
  case TS_EVENT_ERROR:
  case TS_EVENT_VCONN_EOS:
    data->eos_detected = true;
    TSStatIntIncrement(icap_response_err, 1);
    return handle_invalid_icap_behavior(contp, data, "Invalid ICAP server reply: reading icap header");
  case TS_EVENT_VCONN_READ_READY: {
    TSIOBufferReader reader = data->icap_resp_reader;
    int64_t avail;
    int64_t consumed    = data->icap_header.size();
    int64_t read_nbytes = INT64_MAX;

    while (read_nbytes > 0) {
      TSIOBufferBlock blk = TSIOBufferReaderStart(reader);
      char *buf           = const_cast<char *>(TSIOBufferBlockReadStart(blk, reader, &avail));
      int64_t read_ndone  = (avail >= read_nbytes) ? read_nbytes : avail;
      int64_t consume     = read_ndone;
      std::string chunk   = std::string(buf, read_ndone);

      /* Read in the icap header */
      data->icap_header += chunk;
      // TSDebug(PLUGIN_NAME, "Headers: \n%s", icap_header.c_str());
      int64_t pos          = data->icap_header.find("\r\n\r\n");
      int64_t token_length = std::string("\r\n\r\n").size();

      if (pos != static_cast<int64_t>(std::string::npos)) {
        data->icap_header.resize(pos);
        consume = pos + token_length - consumed;
        TSIOBufferReaderConsume(reader, consume);
        if (handle_icap_headers(contp, data)) {
          return transform_send_os_resp(contp, data);
        } else {
          return transform_read_http_header(contp, data);
        }
      }

      if (read_ndone > 0) {
        read_nbytes -= consume;
        TSIOBufferReaderConsume(reader, consume);
        consumed += consume;
      } else {
        break;
      }
    }
    break;
  }
  default:
    break;
  }

  return 0;
}

static int
transform_read_http_header_event(TSCont contp, TransformData *data, TSEvent event, void *edata)
{
  switch (event) {
  case TS_EVENT_ERROR:
    return handle_invalid_icap_behavior(contp, data, "Error when reading http header");
  case TS_EVENT_VCONN_EOS:
    data->eos_detected = true;
    TSStatIntIncrement(icap_response_err, 1);
    return handle_invalid_icap_behavior(contp, data, "Error when reading http header");
  case TS_EVENT_VCONN_READ_READY: {
    TSIOBufferReader reader = data->icap_resp_reader;
    int64_t avail;
    int64_t consumed    = data->http_header.size();
    int64_t read_nbytes = INT64_MAX;

    while (read_nbytes > 0) {
      TSIOBufferBlock blk = TSIOBufferReaderStart(reader);
      char *buf           = const_cast<char *>(TSIOBufferBlockReadStart(blk, reader, &avail));
      int64_t read_ndone  = (avail >= read_nbytes) ? read_nbytes : avail;
      int64_t consume     = read_ndone;
      std::string chunk   = std::string(buf, read_ndone);

      data->http_header += chunk;
      // TSDebug(PLUGIN_NAME, "Headers: \n%s", icap_header.c_str());
      int64_t pos          = data->http_header.find("\r\n\r\n");
      int64_t token_length = std::string("\r\n").size();

      if (pos != static_cast<int64_t>(std::string::npos)) {
        data->http_header.resize(pos);
        consume = pos + token_length - consumed;
        TSIOBufferReaderConsume(reader, consume);
        handle_icap_http_header(data);
        return transform_read_http_body(contp, data);
      }

      if (read_ndone > 0) {
        read_nbytes -= consume;
        TSIOBufferReaderConsume(reader, consume);
        consumed += consume;
      } else {
        break;
      }
    }

    if (read_nbytes <= 0) {
      /* In case of finish reading http header, start reading http body length */
      return transform_read_http_body(contp, data);
    }
  }
  default:
    break;
  }

  return 0;
}

static int
transform_read_http_body_event(TSCont contp, TransformData *data, TSEvent event, void *edata)
{
  switch (event) {
  case TS_EVENT_ERROR:
    TSVConnAbort(data->icap_vc, 1);
    data->icap_vc  = nullptr;
    data->icap_vio = nullptr;

    TSVConnAbort(data->output_vc, 1);
    data->output_vc  = nullptr;
    data->output_vio = nullptr;
    break;
  case TS_EVENT_VCONN_EOS:
    TSVConnShutdown(data->icap_vc, 1, 0);
    TSVIOReenable(data->output_vio);
    data->eos_detected = true;
    break;
  case TS_EVENT_VCONN_READ_READY:
    handle_read_http_body(contp, data);
    TSVIOReenable(data->output_vio);
    break;
  case TS_EVENT_VCONN_WRITE_COMPLETE:
    TSVConnShutdown(data->output_vc, 0, 1);
    break;
  case TS_EVENT_VCONN_WRITE_READY:
    TSVIOReenable(data->icap_vio);
    handle_read_http_body(contp, data);
    break;
  default:
    break;
  }

  return 0;
}

static int
transform_send_error_msg_event(TSCont contp, TransformData *data, TSEvent event, void *edata)
{
  switch (event) {
  case TS_EVENT_VCONN_WRITE_COMPLETE:
    TSVConnShutdown(data->output_vc, 0, 1);
    break;
  case TS_EVENT_VCONN_WRITE_READY:
  default:
    TSVIOReenable(data->output_vio);
    break;
  }

  return 0;
}

static int
transform_bypass_event(TSCont contp, TransformData *data, TSEvent event, void *edata)
{
  switch (event) {
  case TS_EVENT_VCONN_WRITE_COMPLETE:
    TSVConnShutdown(data->output_vc, 0, 1);
    break;
  default:
    TSVIOReenable(data->output_vio);
    break;
  }

  return 0;
}

/* Used only for debug-mode */
static int
transform_buffer_os_resp_event(TSCont contp, TransformData *data, TSEvent event, void *edata)
{
  TSVIO write_vio;
  int64_t towrite;

  write_vio = TSVConnWriteVIOGet(contp);
  /* check if the write VIO's buffer is non-NULL. */
  if (!TSVIOBufferGet(write_vio)) {
    return transform_send_os_resp(contp, data);
  }

  towrite = TSVIONTodoGet(write_vio);

  if (towrite > 0) {
    /* The amount of data left to read needs to be truncated by
       the amount of data actually in the read buffer. */
    int64_t avail = TSIOBufferReaderAvail(TSVIOReaderGet(write_vio));
    if (towrite > avail) {
      towrite = avail;
    }
    if (towrite > 0) {
      /* Copy the data from the read buffer to the input buffer. */
      TSIOBufferCopy(data->os_resp_buf, TSVIOReaderGet(write_vio), towrite, 0);

      /* Tell the read buffer that we have read the data and are no
         longer interested in it. */
      TSIOBufferReaderConsume(TSVIOReaderGet(write_vio), towrite);

      /* Modify the write VIO to reflect how much data we've
         completed. */
      TSVIONDoneSet(write_vio, TSVIONDoneGet(write_vio) + towrite);
    }
  }

  /* Now we check the write VIO to see if there is data left to
     read. */
  if (TSVIONTodoGet(write_vio) > 0) {
    /* Call back the write VIO continuation to let it know that we
       are ready for more data. */
    TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_READY, write_vio);
  } else {
    /* Call back the write VIO continuation to let it know that we
       have completed the write operation. */
    TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_COMPLETE, write_vio);

    return transform_send_os_resp(contp, data);
  }

  return 0;
}

/* Used only for debug-mode */
static int
transform_send_os_resp_event(TSCont contp, TransformData *data, TSEvent event, void *edata)
{
  switch (event) {
  case TS_EVENT_VCONN_WRITE_COMPLETE:
    TSVConnShutdown(data->output_vc, 0, 1);
    break;
  default:
    TSVIOReenable(data->output_vio);
    break;
  }

  return 0;
}

static int
transform_handler(TSCont contp, TSEvent event, void *edata)
{
  /* Check to see if the transformation has been closed by a call to TSVConnClose. */
  if (TSVConnClosedGet(contp)) {
    TSDebug(PLUGIN_NAME, "transformation closed");
    transform_destroy(contp);
    return 0;
  } else {
    TransformData *data;

    data = static_cast<TransformData *>(TSContDataGet(contp));
    if (data == nullptr) {
      TSError("[%s] Didn't get Continuation's Data, ignoring event", PLUGIN_NAME);
      return 0;
    }
    TSDebug(PLUGIN_NAME, "transform handler event [%d], data->state = [%d]", event, static_cast<int>(data->state));

    switch (data->state) {
    case State::BEGIN:
      transform_connect(contp, data);
      break;
    case State::CONNECT:
      transform_connect_event(contp, data, event, edata);
      break;
    case State::WRITE_HEADER:
      transform_write_header_event(contp, data, event, edata);
      break;
    case State::WRITE_BODY:
      transform_write_event(contp, data, event, edata);
      break;
    case State::READ_ICAP_HEADER:
      transform_read_icap_header_event(contp, data, event, edata);
      break;
    case State::READ_HTTP_HEADER:
      transform_read_http_header_event(contp, data, event, edata);
      break;
    case State::READ_HTTP_BODY:
      transform_read_http_body_event(contp, data, event, edata);
      break;
    case State::SEND_ERROR_MSG:
      transform_send_error_msg_event(contp, data, event, edata);
      break;
    case State::BYPASS:
      transform_bypass_event(contp, data, event, edata);
      break;
    case State::BUFFER_OS_RESP:
      transform_buffer_os_resp_event(contp, data, event, edata);
      break;
    case State::SEND_OS_RESP:
      transform_send_os_resp_event(contp, data, event, edata);
      break;
    }
  }

  return 0;
}

static int
request_ok(TSHttpTxn txnp)
{
  /* Is the initial client request OK for transformation. This is a
     good place to check accept headers to see if the client can
     accept a transformed document. */
  return 1;
}

static int
server_response_ok(TSHttpTxn txnp)
{
  /* Is the response the server sent OK for transformation. This is
   * a good place to check the server's response to see if it is
   * transformable. In this example, we will transform only "200 OK"
   * responses.
   */

  TSMBuffer bufp;
  TSMLoc hdr_loc;
  TSHttpStatus resp_status;

  /* Check if incoming port is carp port, in which case don't initiate
   * transform.
   */
  if (carp_port == get_port(TSHttpTxnServerAddrGet(txnp))) {
    return 0;
  }

  if (TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
    TSError("[%s] Unable to get handle to Server Response", PLUGIN_NAME);
    return 0;
  }

  resp_status = TSHttpHdrStatusGet(bufp, hdr_loc);
  if (TS_HTTP_STATUS_OK == resp_status) {
    if (TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc) != TS_SUCCESS) {
      TSError("[%s] Unable to release handle to server request", PLUGIN_NAME);
    }
    return 1;
  } else {
    if (TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc) != TS_SUCCESS) {
      TSError("[%s] Unable to release handle to server request", PLUGIN_NAME);
    }
    return 0;
  }
}

static int
transform_plugin(TSCont contp, TSEvent event, void *edata)
{
  TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);

  switch (event) {
  case TS_EVENT_HTTP_READ_REQUEST_HDR:
    if (request_ok(txnp)) {
      TSHttpTxnHookAdd(txnp, TS_HTTP_READ_RESPONSE_HDR_HOOK, contp);
    }
    TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
    break;
  case TS_EVENT_HTTP_READ_RESPONSE_HDR:
    if (server_response_ok(txnp)) {
      TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, transform_create(txnp));
    }
    TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
    break;
  default:
    break;
  }
  return 0;
}

void
TSPluginInit(int argc, const char *argv[])
{
  TSPluginRegistrationInfo info;
  TSCont cont;

  info.plugin_name   = PLUGIN_NAME;
  info.vendor_name   = "Apache Software Foundation";
  info.support_email = "dev@trafficserver.apache.org";

  if (TSPluginRegister(&info) != TS_SUCCESS) {
    TSError("[%s] Plugin registration failed", PLUGIN_NAME);
  }

  server_ip     = std::string(argv[1]);
  server_port   = std::stoi(argv[2]);
  carp_port     = std::stoi(argv[3]);
  debug_enabled = std::stoi(argv[4]);

  /* Initialize stats */
  if (TSStatFindName("plugin." PLUGIN_NAME ".scan_passed", &scan_passed) == TS_ERROR) {
    scan_passed = TSStatCreate("plugin." PLUGIN_NAME ".scan_passed", TS_RECORDDATATYPE_INT, TS_STAT_PERSISTENT, TS_STAT_SYNC_COUNT);
  }

  if (TSStatFindName("plugin." PLUGIN_NAME ".scan_failed", &scan_failed) == TS_ERROR) {
    scan_failed = TSStatCreate("plugin." PLUGIN_NAME ".scan_failed", TS_RECORDDATATYPE_INT, TS_STAT_PERSISTENT, TS_STAT_SYNC_COUNT);
  }

  if (TSStatFindName("plugin." PLUGIN_NAME ".icap_conn_failed", &icap_conn_failed) == TS_ERROR) {
    icap_conn_failed =
      TSStatCreate("plugin." PLUGIN_NAME ".icap_conn_failed", TS_RECORDDATATYPE_INT, TS_STAT_PERSISTENT, TS_STAT_SYNC_COUNT);
  }

  if (TSStatFindName("plugin." PLUGIN_NAME ".total_icap_invalid", &total_icap_invalid) == TS_ERROR) {
    total_icap_invalid =
      TSStatCreate("plugin." PLUGIN_NAME ".total_icap_invalid", TS_RECORDDATATYPE_INT, TS_STAT_PERSISTENT, TS_STAT_SYNC_COUNT);
  }

  if (TSStatFindName("plugin." PLUGIN_NAME ".icap_response_err", &icap_response_err) == TS_ERROR) {
    icap_response_err =
      TSStatCreate("plugin." PLUGIN_NAME ".icap_response_err", TS_RECORDDATATYPE_INT, TS_STAT_PERSISTENT, TS_STAT_SYNC_COUNT);
  }

  if (TSStatFindName("plugin." PLUGIN_NAME ".icap_write_failed", &icap_write_failed) == TS_ERROR) {
    icap_write_failed =
      TSStatCreate("plugin." PLUGIN_NAME ".icap_write_failed", TS_RECORDDATATYPE_INT, TS_STAT_PERSISTENT, TS_STAT_SYNC_COUNT);
  }

  TSStatIntSet(scan_passed, 0);
  TSStatIntSet(scan_failed, 0);
  TSStatIntSet(icap_conn_failed, 0);
  TSStatIntSet(icap_write_failed, 0);
  TSStatIntSet(icap_response_err, 0);
  TSStatIntSet(total_icap_invalid, 0);

  cont = TSContCreate(transform_plugin, nullptr);
  TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, cont);
}