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.
   */
  
  /* server-transform.c:  an example program that sends response content
   *                      to a server to be transformed, and sends the
   *                      transformed content to the client
   *
   *
   *	Usage:
   *	(NT): ServerTransform.dll
   *	(Solaris): server-transform.so
   *
   *
   */
  
  /* The protocol spoken with the server is simple. The plugin sends the
     content-length of the document being transformed as a 4-byte
     integer and then it sends the document itself. The first 4-bytes of
     the server response are a status code/content length. If the code
     is greater than 0 then the plugin assumes transformation was
     successful and uses the code as the content length of the
     transformed document. If the status code is less than or equal to 0
     then the plugin bypasses transformation and sends the original
     document on through.
  
     The plugin does a fair amount of error checking and tries to bypass
     transformation in many cases such as when it can't connect to the
     server. This example plugin simply connects to port 7 on localhost,
     which on our solaris machines (and most unix machines) is the echo
     port. One nicety about the protocol is that simply having the
     server echo back what it is sent results in a "null"
     transformation. (i.e. A transformation which does not modify the
     content). */
  
  #include <string.h>
  #include <stdio.h>
  
  #if !defined (_WIN32)
  #  include <netinet/in.h>
  #else
  #  include <windows.h>
  #endif
  
  #include <ts/ts.h>
  
  #define STATE_BUFFER       1
  #define STATE_CONNECT      2
  #define STATE_WRITE        3
  #define STATE_READ_STATUS  4
  #define STATE_READ         5
  #define STATE_BYPASS       6
  
  typedef struct
  {
    int state;
    TSHttpTxn txn;
  
    TSIOBuffer input_buf;
    TSIOBufferReader input_reader;
  
    TSIOBuffer output_buf;
    TSIOBufferReader output_reader;
    TSVConn output_vc;
    TSVIO output_vio;
  
    TSAction pending_action;
    TSVConn server_vc;
    TSVIO server_vio;
  
    int content_length;
  } TransformData;
  
  static TSCont transform_create(TSHttpTxn txnp);
  static void transform_destroy(TSCont contp);
  static int transform_connect(TSCont contp, TransformData * data);
  static int transform_write(TSCont contp, TransformData * data);
  static int transform_read_status(TSCont contp, TransformData * data);
  static int transform_read(TSCont contp, TransformData * data);
  static int transform_bypass(TSCont contp, TransformData * data);
  static int transform_buffer_event(TSCont contp, TransformData * data, TSEvent event, void *edata);
  static int transform_connect_event(TSCont contp, TransformData * data, TSEvent event, void *edata);
  static int transform_write_event(TSCont contp, TransformData * data, TSEvent event, void *edata);
  static int transform_read_status_event(TSCont contp, TransformData * data, TSEvent event, void *edata);
  static int transform_read_event(TSCont contp, TransformData * data, TSEvent event, void *edata);
  static int transform_bypass_event(TSCont contp, TransformData * data, TSEvent event, void *edata);
  static int transform_handler(TSCont contp, TSEvent event, void *edata);
  
  #if !defined (_WIN32)
  static in_addr_t server_ip;
  #else
  static unsigned int server_ip;
  #endif
  
  static int server_port;
  
  static TSCont
  transform_create(TSHttpTxn txnp)
  {
    TSCont contp;
    TransformData *data;
  
    contp = TSTransformCreate(transform_handler, txnp);
  
    data = (TransformData *) TSmalloc(sizeof(TransformData));
    data->state = STATE_BUFFER;
    data->txn = txnp;
    data->input_buf = NULL;
    data->input_reader = NULL;
    data->output_buf = NULL;
    data->output_reader = NULL;
    data->output_vio = NULL;
    data->output_vc = NULL;
    data->pending_action = NULL;
    data->server_vc = NULL;
    data->server_vio = NULL;
    data->content_length = 0;
  
    TSContDataSet(contp, data);
    return contp;
  }
  
  static void
  transform_destroy(TSCont contp)
  {
    TransformData *data;
  
    data = TSContDataGet(contp);
    if (data != NULL) {
      if (data->input_buf)
        TSIOBufferDestroy(data->input_buf);
  
      if (data->output_buf)
        TSIOBufferDestroy(data->output_buf);
  
      if (data->pending_action)
        TSActionCancel(data->pending_action);
  
      if (data->server_vc)
        TSVConnAbort(data->server_vc, 1);
  
      TSfree(data);
    } else {
      TSError("Unable to get Continuation's Data. TSContDataGet returns NULL");
    }
  
    TSContDestroy(contp);
  }
  
  static int
  transform_connect(TSCont contp, TransformData * data)
  {
    TSAction action;
    int content_length;
    struct sockaddr_in ip_addr;
  
    data->state = STATE_CONNECT;
  
    content_length = TSIOBufferReaderAvail(data->input_reader);
    if (content_length >= 0) {
      data->content_length = content_length;
      data->content_length = htonl(data->content_length);
  
      /* Prepend the content length to the buffer.
       * If we decide to not send the content to the transforming
       * server then we need to make sure and skip input_reader
       * over the content length.
       */
  
      {
        TSIOBuffer temp;
        TSIOBufferReader tempReader;
  
        temp = TSIOBufferCreate();
        tempReader = TSIOBufferReaderAlloc(temp);
  
        TSIOBufferWrite(temp, (const char *) &data->content_length, sizeof(int));
        TSIOBufferCopy(temp, data->input_reader, data->content_length, 0);
  
        TSIOBufferReaderFree(data->input_reader);
        TSIOBufferDestroy(data->input_buf);
        data->input_buf = temp;
        data->input_reader = tempReader;
      }
    } else {
      TSError("TSIOBufferReaderAvail returns TS_ERROR");
      return 0;
    }
  
    /* TODO: This only supports IPv4, probably should be changed at some point, but
       it's an example ... */
    memset(&ip_addr, 0, sizeof(ip_addr));
    ip_addr.sin_family = AF_INET;
    ip_addr.sin_addr.s_addr = server_ip; /* Should be in network byte order */
    ip_addr.sin_port = server_port;
    action = TSNetConnect(contp, (struct sockaddr const*)&ip_addr);
  
    if (!TSActionDone(action)) {
      data->pending_action = action;
    }
  
    return 0;
  }
  
  static int
  transform_write(TSCont contp, TransformData * data)
  {
    int content_length;
  
    data->state = STATE_WRITE;
  
    content_length = TSIOBufferReaderAvail(data->input_reader);
    if (content_length >= 0) {
      data->server_vio = TSVConnWrite(data->server_vc, contp, TSIOBufferReaderClone(data->input_reader), content_length);
    } else {
      TSError("TSIOBufferReaderAvail returns TS_ERROR");
    }
    return 0;
  }
  
  static int
  transform_read_status(TSCont contp, TransformData * data)
  {
    data->state = STATE_READ_STATUS;
  
    data->output_buf = TSIOBufferCreate();
    data->output_reader = TSIOBufferReaderAlloc(data->output_buf);
    if (data->output_reader != NULL) {
      data->server_vio = TSVConnRead(data->server_vc, contp, data->output_buf, sizeof(int));
    } else {
      TSError("Error in Allocating a Reader to output buffer. TSIOBufferReaderAlloc returns NULL");
    }
  
    return 0;
  }
  
  static int
  transform_read(TSCont contp, TransformData * data)
  {
    data->state = STATE_READ;
  
    TSIOBufferDestroy(data->input_buf);
    data->input_buf = NULL;
    data->input_reader = NULL;
  
    data->server_vio = TSVConnRead(data->server_vc, contp, data->output_buf, data->content_length);
    data->output_vc = TSTransformOutputVConnGet((TSVConn) contp);
    if (data->output_vc == NULL) {
      TSError("TSTransformOutputVConnGet returns NULL");
    } else {
      data->output_vio = TSVConnWrite(data->output_vc, contp, data->output_reader, data->content_length);
      if (data->output_vio == NULL) {
        TSError("TSVConnWrite returns NULL");
      }
    }
  
    return 0;
  }
  
  static int
  transform_bypass(TSCont contp, TransformData * data)
  {
    data->state = STATE_BYPASS;
  
    if (data->server_vc) {
      TSVConnAbort(data->server_vc, 1);
      data->server_vc = NULL;
      data->server_vio = NULL;
    }
  
    if (data->output_buf) {
      TSIOBufferDestroy(data->output_buf);
      data->output_buf = NULL;
      data->output_reader = NULL;
    }
  
    TSIOBufferReaderConsume(data->input_reader, sizeof(int));
    data->output_vc = TSTransformOutputVConnGet((TSVConn) contp);
    if (data->output_vc == NULL) {
      TSError("TSTransformOutputVConnGet returns NULL");
    } else {
      data->output_vio =
        TSVConnWrite(data->output_vc, contp, data->input_reader, TSIOBufferReaderAvail(data->input_reader));
      if (data->output_vio == NULL) {
        TSError("TSVConnWrite returns NULL");
      }
    }
    return 1;
  }
  
  static int
  transform_buffer_event(TSCont contp, TransformData * data, TSEvent event, void *edata)
  {
    TSVIO write_vio;
    int towrite;
    int avail;
  
    if (!data->input_buf) {
      data->input_buf = TSIOBufferCreate();
      data->input_reader = TSIOBufferReaderAlloc(data->input_buf);
    }
  
    /* Get the write VIO for the write operation that was performed on
       ourself. This VIO contains the buffer that we are to read from
       as well as the continuation we are to call when the buffer is
       empty. */
    write_vio = TSVConnWriteVIOGet(contp);
  
    /* We also check to see if the write VIO's buffer is non-NULL. A
       NULL buffer indicates that the write operation has been
       shutdown and that the continuation does not want us to send any
       more WRITE_READY or WRITE_COMPLETE events. For this buffered
       transformation that means we're done buffering data. */
    if (!TSVIOBufferGet(write_vio)) {
      return transform_connect(contp, data);
    }
  
    /* Determine how much data we have left to read. For this server
       transform plugin this is also the amount of data we have left
       to write to the output connection. */
    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. */
      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->input_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);
  
      /* start compression... */
      return transform_connect(contp, data);
    }
  
    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 = NULL;
      data->server_vc = (TSVConn) edata;
      return transform_write(contp, data);
    case TS_EVENT_NET_CONNECT_FAILED:
      data->pending_action = NULL;
      return transform_bypass(contp, data);
    default:
      break;
    }
  
    return 0;
  }
  
  static int
  transform_write_event(TSCont contp, TransformData * data, TSEvent event, void *edata)
  {
    switch (event) {
    case TS_EVENT_VCONN_WRITE_READY:
      TSVIOReenable(data->server_vio);
      break;
    case TS_EVENT_VCONN_WRITE_COMPLETE:
      return transform_read_status(contp, data);
    default:
      /* An error occurred while writing to the server. Close down
         the connection to the server and bypass. */
      return transform_bypass(contp, data);
    }
  
    return 0;
  }
  
  static int
  transform_read_status_event(TSCont contp, TransformData * data, TSEvent event, void *edata)
  {
    switch (event) {
    case TS_EVENT_ERROR:
    case TS_EVENT_VCONN_EOS:
      return transform_bypass(contp, data);
    case TS_EVENT_VCONN_READ_COMPLETE:
      if (TSIOBufferReaderAvail(data->output_reader) == sizeof(int)) {
        TSIOBufferBlock blk;
        char *buf;
        void *buf_ptr;
        int64_t avail;
        int64_t read_nbytes = sizeof(int);
        int64_t read_ndone = 0;
  
        buf_ptr = &data->content_length;
        while (read_nbytes > 0) {
          blk = TSIOBufferReaderStart(data->output_reader);
          buf = (char *) TSIOBufferBlockReadStart(blk, data->output_reader, &avail);
          read_ndone = (avail >= read_nbytes) ? read_nbytes : avail;
          memcpy(buf_ptr, buf, read_ndone);
          if (read_ndone > 0) {
            TSIOBufferReaderConsume(data->output_reader, read_ndone);
            read_nbytes -= read_ndone;
            /* move ptr frwd by read_ndone bytes */
            buf_ptr = (char *) buf_ptr + read_ndone;
          }
        }
        data->content_length = ntohl(data->content_length);
        return transform_read(contp, data);
      }
      return transform_bypass(contp, data);
    default:
      break;
    }
  
    return 0;
  }
  
  static int
  transform_read_event(TSCont contp, TransformData * data, TSEvent event, void *edata)
  {
    switch (event) {
    case TS_EVENT_ERROR:
      TSVConnAbort(data->server_vc, 1);
      data->server_vc = NULL;
      data->server_vio = NULL;
  
      TSVConnAbort(data->output_vc, 1);
      data->output_vc = NULL;
      data->output_vio = NULL;
      break;
    case TS_EVENT_VCONN_EOS:
      TSVConnAbort(data->server_vc, 1);
      data->server_vc = NULL;
      data->server_vio = NULL;
  
      TSVConnAbort(data->output_vc, 1);
      data->output_vc = NULL;
      data->output_vio = NULL;
      break;
    case TS_EVENT_VCONN_READ_COMPLETE:
      TSVConnClose(data->server_vc);
      data->server_vc = NULL;
      data->server_vio = NULL;
  
      TSVIOReenable(data->output_vio);
      break;
    case TS_EVENT_VCONN_READ_READY:
      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->server_vio);
      break;
    default:
      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;
    case TS_EVENT_VCONN_WRITE_READY:
    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)) {
      transform_destroy(contp);
      return 0;
    } else {
      TransformData *data;
      int val = 0;
  
      data = (TransformData *)TSContDataGet(contp);
      if (data == NULL) {
        TSError("Didn't get Continuation's Data. Ignoring Event..");
        return 0;
      }
      do {
        switch (data->state) {
        case STATE_BUFFER:
          val = transform_buffer_event(contp, data, event, edata);
          break;
        case STATE_CONNECT:
          val = transform_connect_event(contp, data, event, edata);
          break;
        case STATE_WRITE:
          val = transform_write_event(contp, data, event, edata);
          break;
        case STATE_READ_STATUS:
          val = transform_read_status_event(contp, data, event, edata);
          break;
        case STATE_READ:
          val = transform_read_event(contp, data, event, edata);
          break;
        case STATE_BYPASS:
          val = transform_bypass_event(contp, data, event, edata);
          break;
        }
      } while (val);
    }
  
    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
  cache_response_ok(TSHttpTxn txnp)
  {
    /* Is the response we're reading from cache OK for
     * transformation. This is a good place to check the cached
     * response to see if it is transformable. The default
     * behavior is to cache transformed content; therefore
     * to avoid transforming twice we will not transform
     * content served from the cache.
     */
    return 0;
  }
  
  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;
  
    if (TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
      TSError("Unable to get handle to Server Response");
      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("Unable to release handle to server request");
      }
      return 1;
    } else {
      if (TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc) != TS_SUCCESS) {
        TSError("Unable to release handle to server request");
      }
      return 0;
    }
  }
  
  static int
  transform_plugin(TSCont contp, TSEvent event, void *edata)
  {
    TSHttpTxn txnp = (TSHttpTxn) edata;
  
    switch (event) {
    case TS_EVENT_HTTP_READ_REQUEST_HDR:
      if (request_ok(txnp)) {
        TSHttpTxnHookAdd(txnp, TS_HTTP_READ_CACHE_HDR_HOOK, contp);
        TSHttpTxnHookAdd(txnp, TS_HTTP_READ_RESPONSE_HDR_HOOK, contp);
      }
      TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
      break;
    case TS_EVENT_HTTP_READ_CACHE_HDR:
      if (cache_response_ok(txnp)) {
        TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, transform_create(txnp));
      }
      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;
  }
  
  int
  check_ts_version()
  {
  
    const char *ts_version = TSTrafficServerVersionGet();
    int result = 0;
  
    if (ts_version) {
      int major_ts_version = 0;
      int minor_ts_version = 0;
      int patch_ts_version = 0;
  
      if (sscanf(ts_version, "%d.%d.%d", &major_ts_version, &minor_ts_version, &patch_ts_version) != 3) {
        return 0;
      }
  
      /* Since this is an TS-SDK 2.0 plugin, we need at
         least Traffic Server 2.0 to run */
      if (major_ts_version >= 2) {
        result = 1;
      }
    }
  
    return result;
  }
  
  void
  TSPluginInit(int argc, const char *argv[])
  {
    TSPluginRegistrationInfo info;
    TSCont cont;
  
    info.plugin_name = "server-transform";
    info.vendor_name = "MyCompany";
    info.support_email = "ts-api-support@MyCompany.com";
  
    if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) {
      TSError("Plugin registration failed.\n");
    }
  
    if (!check_ts_version()) {
      TSError("Plugin requires Traffic Server 3.0 or later\n");
      return;
    }
  
    /* connect to the echo port on localhost */
    server_ip = (127 << 24) | (0 << 16) | (0 << 8) | (1);
    server_ip = htonl(server_ip);
    server_port = 7;
  
    cont = TSContCreate(transform_plugin, NULL);
    TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, cont);
  }
  