text: /** @file
  
    A brief file description
  
    @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 "ts/ink_platform.h"
  #include "ts/ink_args.h"
  #include "ts/I_Version.h"
  #include "ts/Tokenizer.h"
  #include "ts/TextBuffer.h"
  #include "mgmtapi.h"
  #include <cstdio>
  #include <cstring>
  #include "ts/Regex.h"
  
  /// XXX Use DFA or Regex wrappers?
  #ifdef HAVE_PCRE_PCRE_H
  #include <pcre/pcre.h>
  #else
  #include <pcre.h>
  #endif
  
  #define SUBSTRING_VECTOR_COUNT 30 // Should be multiple of 3
  
  static AppVersionInfo appVersionInfo;
  
  struct VIA {
    VIA(const char *t) : title(t), next(nullptr) { memset(viaData, 0, sizeof(viaData)); }
    ~VIA() { delete next; }
    const char *title;
    const char *viaData[128];
    VIA *next;
  };
  
  // Function to get via header table for every field/category in the via header
  static VIA *
  detailViaLookup(char flag)
  {
    VIA *viaTable;
  
    // Detailed via codes after ":"
    switch (flag) {
    case 't':
      viaTable                              = new VIA("Tunnel info");
      viaTable->viaData[(unsigned char)' '] = "no tunneling";
      viaTable->viaData[(unsigned char)'U'] = "tunneling because of url (url suggests dynamic content)";
      viaTable->viaData[(unsigned char)'M'] = "tunneling due to a method (e.g. CONNECT)";
      viaTable->viaData[(unsigned char)'O'] = "tunneling because cache is turned off";
      viaTable->viaData[(unsigned char)'F'] = "tunneling due to a header field (such as presence of If-Range header)";
      viaTable->viaData[(unsigned char)'N'] = "tunneling due to no forward";
      viaTable->viaData[(unsigned char)'A'] = "tunnel authorization";
      break;
    case 'c':
      // Cache type
      viaTable                              = new VIA("Cache Type");
      viaTable->viaData[(unsigned char)'C'] = "cache";
      viaTable->viaData[(unsigned char)'L'] = "cluster, (not used)";
      viaTable->viaData[(unsigned char)'I'] = "icp";
      viaTable->viaData[(unsigned char)'P'] = "parent";
      viaTable->viaData[(unsigned char)'S'] = "server";
      viaTable->viaData[(unsigned char)' '] = "unknown";
  
      // Cache Lookup Result
      viaTable->next                              = new VIA("Cache Lookup Result");
      viaTable->next->viaData[(unsigned char)'C'] = "cache hit but config forces revalidate";
      viaTable->next->viaData[(unsigned char)'I'] = "conditional miss (client sent conditional, fresh in cache, returned 412)";
      viaTable->next->viaData[(unsigned char)' '] = "cache miss or no cache lookup";
      viaTable->next->viaData[(unsigned char)'U'] = "cache hit, but client forces revalidate (e.g. Pragma: no-cache)";
      viaTable->next->viaData[(unsigned char)'D'] = "cache hit, but method forces revalidated (e.g. ftp, not anonymous)";
      viaTable->next->viaData[(unsigned char)'M'] = "cache miss (url not in cache)";
      viaTable->next->viaData[(unsigned char)'N'] = "conditional hit (client sent conditional, doc fresh in cache, returned 304)";
      viaTable->next->viaData[(unsigned char)'H'] = "cache hit";
      viaTable->next->viaData[(unsigned char)'S'] = "cache hit, but expired";
      viaTable->next->viaData[(unsigned char)'K'] = "cookie miss";
      break;
    case 'i':
      viaTable                              = new VIA("ICP status");
      viaTable->viaData[(unsigned char)' '] = "no icp";
      viaTable->viaData[(unsigned char)'S'] = "connection opened successfully";
      viaTable->viaData[(unsigned char)'F'] = "connection open failed";
      break;
    case 'p':
      viaTable                              = new VIA("Parent proxy connection status");
      viaTable->viaData[(unsigned char)' '] = "no parent proxy or unknown";
      viaTable->viaData[(unsigned char)'S'] = "connection opened successfully";
      viaTable->viaData[(unsigned char)'F'] = "connection open failed";
      break;
    case 's':
      viaTable                              = new VIA("Origin server connection status");
      viaTable->viaData[(unsigned char)' '] = "no server connection needed";
      viaTable->viaData[(unsigned char)'S'] = "connection opened successfully";
      viaTable->viaData[(unsigned char)'F'] = "connection open failed";
      break;
    default:
      viaTable = nullptr;
      fprintf(stderr, "%s: %s: %c\n", appVersionInfo.AppStr, "Invalid VIA header character", flag);
      break;
    }
  
    return viaTable;
  }
  
  // Function to get via header table for every field/category in the via header
  static VIA *
  standardViaLookup(char flag)
  {
    VIA *viaTable;
  
    // Via codes before ":"
    switch (flag) {
    case 'u':
      viaTable                              = new VIA("Request headers received from client");
      viaTable->viaData[(unsigned char)'C'] = "cookie";
      viaTable->viaData[(unsigned char)'E'] = "error in request";
      viaTable->viaData[(unsigned char)'S'] = "simple request (not conditional)";
      viaTable->viaData[(unsigned char)'N'] = "no-cache";
      viaTable->viaData[(unsigned char)'I'] = "IMS";
      viaTable->viaData[(unsigned char)' '] = "unknown";
      break;
    case 'c':
      viaTable                              = new VIA("Result of Traffic Server cache lookup for URL");
      viaTable->viaData[(unsigned char)'A'] = "in cache, not acceptable (a cache \"MISS\")";
      viaTable->viaData[(unsigned char)'H'] = "in cache, fresh (a cache \"HIT\")";
      viaTable->viaData[(unsigned char)'S'] = "in cache, stale (a cache \"MISS\")";
      viaTable->viaData[(unsigned char)'R'] = "in cache, fresh Ram hit (a cache \"HIT\")";
      viaTable->viaData[(unsigned char)'M'] = "miss (a cache \"MISS\")";
      viaTable->viaData[(unsigned char)' '] = "no cache lookup";
      break;
    case 's':
      viaTable                              = new VIA("Response information received from origin server");
      viaTable->viaData[(unsigned char)'E'] = "error in response";
      viaTable->viaData[(unsigned char)'S'] = "connection opened successfully";
      viaTable->viaData[(unsigned char)'N'] = "not-modified";
      viaTable->viaData[(unsigned char)' '] = "no server connection needed";
      break;
    case 'f':
      viaTable                              = new VIA("Result of document write-to-cache:");
      viaTable->viaData[(unsigned char)'U'] = "updated old cache copy";
      viaTable->viaData[(unsigned char)'D'] = "cached copy deleted";
      viaTable->viaData[(unsigned char)'W'] = "written into cache (new copy)";
      viaTable->viaData[(unsigned char)' '] = "no cache write performed";
      break;
    case 'p':
      viaTable                              = new VIA("Proxy operation result");
      viaTable->viaData[(unsigned char)'R'] = "origin server revalidated";
      viaTable->viaData[(unsigned char)' '] = "unknown";
      viaTable->viaData[(unsigned char)'S'] = "served or connection opened successfully";
      viaTable->viaData[(unsigned char)'N'] = "not-modified";
      break;
    case 'e':
      viaTable                              = new VIA("Error codes (if any)");
      viaTable->viaData[(unsigned char)'A'] = "authorization failure";
      viaTable->viaData[(unsigned char)'H'] = "header syntax unacceptable";
      viaTable->viaData[(unsigned char)'C'] = "connection to server failed";
      viaTable->viaData[(unsigned char)'T'] = "connection timed out";
      viaTable->viaData[(unsigned char)'S'] = "server related error";
      viaTable->viaData[(unsigned char)'D'] = "dns failure";
      viaTable->viaData[(unsigned char)'N'] = "no error";
      viaTable->viaData[(unsigned char)'F'] = "request forbidden";
      viaTable->viaData[(unsigned char)'R'] = "cache read error";
      viaTable->viaData[(unsigned char)'M'] = "moved temporarily";
      viaTable->viaData[(unsigned char)' '] = "unknown";
      break;
    default:
      viaTable = nullptr;
      fprintf(stderr, "%s: %s: %c\n", appVersionInfo.AppStr, "Invalid VIA header character", flag);
      break;
    }
  
    return viaTable;
  }
  
  // Function to print via header
  static void
  printViaHeader(const char *header)
  {
    VIA *viaTable = nullptr;
    VIA *viaEntry = nullptr;
    bool isDetail = false;
  
    printf("Via Header Details:\n");
  
    // Loop through input via header flags
    for (const char *c = header; *c; ++c) {
      if ((*c == ':') || (*c == ';')) {
        isDetail = true;
        continue;
      }
  
      if (islower(*c)) {
        // Get the via header table
        delete viaTable;
        viaEntry = viaTable = isDetail ? detailViaLookup(*c) : standardViaLookup(*c);
      } else {
        // This is a one of the sequence of (uppercase) VIA codes.
        if (viaEntry) {
          printf("%-55s:", viaEntry->title);
          printf("%s\n", viaEntry->viaData[(unsigned char)*c] ? viaEntry->viaData[(unsigned char)*c] : "Invalid sequence");
          viaEntry = viaEntry->next;
        }
      }
    }
    delete viaTable;
  }
  
  // Check validity of via header and then decode it
  static TSMgmtError
  decodeViaHeader(const char *str)
  {
    size_t viaHdrLength = strlen(str);
    char tmp[viaHdrLength + 2];
    char *Via = tmp;
  
    memcpy(Via, str, viaHdrLength);
    Via[viaHdrLength] = '\0'; // null terminate
    printf("Via header is %s, Length is %zu\n", Via, viaHdrLength);
  
    // Via header inside square brackets
    if (Via[0] == '[' && Via[viaHdrLength - 1] == ']') {
      viaHdrLength = viaHdrLength - 2;
      Via++;
      Via[viaHdrLength] = '\0'; // null terminate the string after trimming
    }
  
    if (viaHdrLength == 5) {
      Via = strcat(Via, " "); // Add one space character before decoding via header
      ++viaHdrLength;
    }
  
    if (viaHdrLength == 24 || viaHdrLength == 6) {
      // Decode via header
      printViaHeader(Via);
      return TS_ERR_OKAY;
    }
    // Invalid header size, come out.
    printf("\nInvalid VIA header. VIA header length should be 6 or 24 characters\n");
    printf("Valid via header format is "
           "[u<client-stuff>c<cache-lookup-stuff>s<server-stuff>f<cache-fill-stuff>p<proxy-stuff>]e<error-codes>:t<tunneling-info>c<"
           "cache type><cache-lookup-result>i<icp-conn-info>p<parent-proxy-conn-info>s<server-conn-info>]");
    return TS_ERR_FAIL;
  }
  
  // Read user input from stdin
  static TSMgmtError
  filterViaHeader()
  {
    const pcre *compiledReg;
    const pcre_extra *extraReg = nullptr;
    int subStringVector[SUBSTRING_VECTOR_COUNT];
    const char *err;
    int errOffset;
    int pcreExecCode;
    int i;
    const char *viaPattern =
      R"(\[([ucsfpe]+[^\]]+)\])"; // Regex to match via header with in [] which can start with character class ucsfpe
    char *viaHeaderString;
    char viaHeader[1024];
  
    // Compile PCRE via header pattern
    compiledReg = pcre_compile(viaPattern, 0, &err, &errOffset, nullptr);
  
    if (compiledReg == nullptr) {
      printf("PCRE regex compilation failed with error %s at offset %d\n", err, errOffset);
      return TS_ERR_FAIL;
    }
  
    // Read all lines from stdin
    while (fgets(viaHeader, sizeof(viaHeader), stdin)) {
      // Trim new line character and null terminate it
      char *newLinePtr = strchr(viaHeader, '\n');
      if (newLinePtr) {
        *newLinePtr = '\0';
      }
      // Match for via header pattern
      pcreExecCode =
        pcre_exec(compiledReg, extraReg, viaHeader, (int)sizeof(viaHeader), 0, 0, subStringVector, SUBSTRING_VECTOR_COUNT);
  
      // Match failed, don't worry. Continue to next line.
      if (pcreExecCode < 0) {
        continue;
      }
  
      // Match successful, but too many substrings
      if (pcreExecCode == 0) {
        pcreExecCode = SUBSTRING_VECTOR_COUNT / 3;
        printf("Too many substrings were found. %d substrings couldn't fit into subStringVector\n", pcreExecCode - 1);
      }
  
      // Loop based on number of matches found
      for (i = 1; i < pcreExecCode; i++) {
        // Point to beginning of matched substring
        char *subStringBegin = viaHeader + subStringVector[2 * i];
        // Get length of matched substring
        int subStringLen = subStringVector[2 * i + 1] - subStringVector[2 * i];
        viaHeaderString  = subStringBegin;
        sprintf(viaHeaderString, "%.*s", subStringLen, subStringBegin);
        // Decode matched substring
        decodeViaHeader(viaHeaderString);
      }
    }
    return TS_ERR_OKAY;
  }
  
  int
  main(int /* argc ATS_UNUSED */, const char **argv)
  {
    TSMgmtError status;
  
    // build the application information structure
    appVersionInfo.setup(PACKAGE_NAME, "traffic_via", PACKAGE_VERSION, __DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, "");
  
    /* see 'ink_args.h' for meanings of the various fields */
    ArgumentDescription argument_descriptions[] = {
      VERSION_ARGUMENT_DESCRIPTION(), HELP_ARGUMENT_DESCRIPTION(),
    };
  
    process_args(&appVersionInfo, argument_descriptions, countof(argument_descriptions), argv);
  
    for (unsigned i = 0; i < n_file_arguments; ++i) {
      if (strcmp(file_arguments[i], "-") == 0) {
        // Filter arguments provided from stdin
        status = filterViaHeader();
      } else {
        status = decodeViaHeader(file_arguments[i]);
      }
  
      if (status != TS_ERR_OKAY) {
        return 1;
      }
    }
  
    return 0;
  }
  