/** @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 <stdio.h>

#if !defined (_WIN32)
#  include <netinet/in.h>
#else
#  include <windows.h>
#endif

#include "InkAPI.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;
  INKHttpTxn txn;

  INKIOBuffer input_buf;
  INKIOBufferReader input_reader;

  INKIOBuffer output_buf;
  INKIOBufferReader output_reader;
  INKVConn output_vc;
  INKVIO output_vio;

  INKAction pending_action;
  INKVConn server_vc;
  INKVIO server_vio;

  int content_length;
} TransformData;

static INKCont transform_create(INKHttpTxn txnp);
static void transform_destroy(INKCont contp);
static int transform_connect(INKCont contp, TransformData * data);
static int transform_write(INKCont contp, TransformData * data);
static int transform_read_status(INKCont contp, TransformData * data);
static int transform_read(INKCont contp, TransformData * data);
static int transform_bypass(INKCont contp, TransformData * data);
static int transform_buffer_event(INKCont contp, TransformData * data, INKEvent event, void *edata);
static int transform_connect_event(INKCont contp, TransformData * data, INKEvent event, void *edata);
static int transform_write_event(INKCont contp, TransformData * data, INKEvent event, void *edata);
static int transform_read_status_event(INKCont contp, TransformData * data, INKEvent event, void *edata);
static int transform_read_event(INKCont contp, TransformData * data, INKEvent event, void *edata);
static int transform_bypass_event(INKCont contp, TransformData * data, INKEvent event, void *edata);
static int transform_handler(INKCont contp, INKEvent event, void *edata);

#if !defined (_WIN32)
static in_addr_t server_ip;
#else
static unsigned int server_ip;
#endif

static int server_port;

static INKCont
transform_create(INKHttpTxn txnp)
{
  INKCont contp;
  TransformData *data;

  if ((contp = INKTransformCreate(transform_handler, txnp)) == INK_ERROR_PTR) {
    INKError("Error in creating Transformation. Retyring...");
    return NULL;
  }

  data = (TransformData *) INKmalloc(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;

  if (INKContDataSet(contp, data) != INK_SUCCESS) {
    INKError("Error in setting continuation's data. INKContDataSet doesn't return INK_SUCCESS");
  }

  return contp;
}

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

  data = INKContDataGet(contp);
  if ((data != INK_ERROR_PTR) || (data != NULL)) {
    if (data->input_buf) {
      if (INKIOBufferDestroy(data->input_buf) != INK_SUCCESS) {
        INKError("Unable to destroy input IO buffer");
      }
    }

    if (data->output_buf) {
      if (INKIOBufferDestroy(data->output_buf) != INK_SUCCESS) {
        INKError("Unable to destroy output IO buffer");
      }
    }

    if (data->pending_action) {
      if (INKActionCancel(data->pending_action) != INK_SUCCESS) {
        INKError("Unable to cancel the pending action");
      }
    }

    if (data->server_vc) {
      if (INKVConnAbort(data->server_vc, 1) != INK_SUCCESS) {
        INKError("Unable to abort server VConnection. INKVConnAbort doesn't return INK_SUCESS");
      }
    }

    INKfree(data);
  } else {
    INKError("Unable to get Continuation's Data. INKContDataGet returns INK_ERROR_PTR or NULL");
  }

  if (INKContDestroy(contp) != INK_SUCCESS) {
    INKError("Error in Destroying the continuation");
  }
}

static int
transform_connect(INKCont contp, TransformData * data)
{
  INKAction action;
  int content_length;

  data->state = STATE_CONNECT;

  content_length = INKIOBufferReaderAvail(data->input_reader);
  if (content_length != INK_ERROR) {
    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.
     */

    {
      INKIOBuffer temp;
      INKIOBufferReader tempReader;

      temp = INKIOBufferCreate();
      if (temp != INK_ERROR_PTR) {
        tempReader = INKIOBufferReaderAlloc(temp);

        if (tempReader != INK_ERROR_PTR) {

          if (INKIOBufferWrite(temp, (const char *) &data->content_length, sizeof(int)) == INK_ERROR) {
            INKError("INKIOBufferWrite returns INK_ERROR");
            if (INKIOBufferReaderFree(tempReader) == INK_ERROR) {
              INKError("INKIOBufferReaderFree returns INK_ERROR");
            }
            if (INKIOBufferDestroy(temp) == INK_ERROR) {
              INKError("INKIOBufferDestroy returns INK_ERROR");
            }
            return 0;
          }

          if (INKIOBufferCopy(temp, data->input_reader, data->content_length, 0) == INK_ERROR) {
            INKError("INKIOBufferCopy returns INK_ERROR");
            if (INKIOBufferReaderFree(tempReader) == INK_ERROR) {
              INKError("INKIOBufferReaderFree returns INK_ERROR");
            }
            if (INKIOBufferDestroy(temp) == INK_ERROR) {
              INKError("INKIOBufferDestroy returns INK_ERROR");
            }
            return 0;
          }

          if (INKIOBufferReaderFree(data->input_reader) == INK_ERROR) {
            INKError("Unable to free IOBuffer Reader");
          }

          if (INKIOBufferDestroy(data->input_buf) == INK_ERROR) {
            INKError("Trying to destroy IOBuffer returns INK_ERROR");
          }

          data->input_buf = temp;
          data->input_reader = tempReader;

        } else {
          INKError("Unable to allocate a reader for buffer");
          if (INKIOBufferDestroy(temp) == INK_ERROR) {
            INKError("Unable to destroy IOBuffer");
          }
          return 0;
        }
      } else {
        INKError("Unable to create IOBuffer.");
        return 0;
      }
    }
  } else {
    INKError("INKIOBufferReaderAvail returns INK_ERROR");
    return 0;
  }

  action = INKNetConnect(contp, server_ip, server_port);
  if (action != INK_ERROR_PTR) {
    if (!INKActionDone(action)) {
      data->pending_action = action;
    }
  } else {
    INKError("Unable to connect to server. INKNetConnect returns INK_ERROR_PTR");
  }

  return 0;
}

static int
transform_write(INKCont contp, TransformData * data)
{
  int content_length;

  data->state = STATE_WRITE;

  content_length = INKIOBufferReaderAvail(data->input_reader);
  if (content_length != INK_ERROR) {

    data->server_vio =
      INKVConnWrite(data->server_vc, contp, INKIOBufferReaderClone(data->input_reader), content_length);
    if (data->server_vio == INK_ERROR_PTR) {
      INKError("INKVConnWrite returns INK_ERROR_PTR");
    }
  } else {
    INKError("INKIOBufferReaderAvail returns INK_ERROR");
  }
  return 0;
}

static int
transform_read_status(INKCont contp, TransformData * data)
{
  data->state = STATE_READ_STATUS;

  data->output_buf = INKIOBufferCreate();
  if ((data->output_buf != NULL) && (data->output_buf != INK_ERROR_PTR)) {
    data->output_reader = INKIOBufferReaderAlloc(data->output_buf);
    if ((data->output_reader != NULL) && (data->output_reader != INK_ERROR_PTR)) {
      data->server_vio = INKVConnRead(data->server_vc, contp, data->output_buf, sizeof(int));
      if (data->server_vio == INK_ERROR_PTR) {
        INKError("INKVConnRead returns INK_ERROR_PTR");
      }

    } else {
      INKError("Error in Allocating a Reader to output buffer. INKIOBufferReaderAlloc returns NULL or INK_ERROR_PTR");
    }
  } else {
    INKError("Error in creating output buffer. INKIOBufferCreate returns INK_ERROR_PTR");
  }
  return 0;
}

static int
transform_read(INKCont contp, TransformData * data)
{
  data->state = STATE_READ;

  if (INKIOBufferDestroy(data->input_buf) != INK_SUCCESS) {
    INKError("Unable to destroy input IO Buffer. INKIOBuffer doesn't return INK_SUCCESS");
  }
  data->input_buf = NULL;
  data->input_reader = NULL;

  data->server_vio = INKVConnRead(data->server_vc, contp, data->output_buf, data->content_length);

  if (data->server_vio == INK_ERROR_PTR) {
    INKError("INKVConnRead returns INK_ERROR_PTR");
    return -1;
  }

  data->output_vc = INKTransformOutputVConnGet((INKVConn) contp);
  if ((data->output_vc == INK_ERROR_PTR) || (data->output_vc == NULL)) {
    INKError("INKTransformOutputVConnGet returns NULL or INK_ERROR_PTR");
  } else {
    data->output_vio = INKVConnWrite(data->output_vc, contp, data->output_reader, data->content_length);
    if ((data->output_vio == INK_ERROR_PTR) || (data->output_vio == NULL)) {
      INKError("INKVConnWrite returns NULL or INK_ERROR_PTR");
    }
  }

  return 0;
}

static int
transform_bypass(INKCont contp, TransformData * data)
{
  data->state = STATE_BYPASS;

  if (data->server_vc) {
    if (INKVConnAbort(data->server_vc, 1) != INK_SUCCESS) {
      INKError("Error in destroy server vc. INKVConnAbort doesn't return INK_SUCCESS");
    }
    data->server_vc = NULL;
    data->server_vio = NULL;
  }

  if (data->output_buf) {
    if (INKIOBufferDestroy(data->output_buf) != INK_SUCCESS) {
      INKError("Error in destroy output IO buffer. INKIOBufferDestroy doesn't return INK_SUCCESS");
    }
    data->output_buf = NULL;
    data->output_reader = NULL;
  }

  if (INKIOBufferReaderConsume(data->input_reader, sizeof(int)) != INK_SUCCESS) {
    INKError("Error in Consuming bytes from Reader. INKIObufferReaderConsume doesn't return INK_SUCCESS");
  }

  data->output_vc = INKTransformOutputVConnGet((INKVConn) contp);
  if ((data->output_vc == INK_ERROR_PTR) || (data->output_vc == NULL)) {
    INKError("INKTransformOutputVConnGet returns NULL or INK_ERROR_PTR");
  } else {
    data->output_vio =
      INKVConnWrite(data->output_vc, contp, data->input_reader, INKIOBufferReaderAvail(data->input_reader));
    if ((data->output_vio == INK_ERROR_PTR) || (data->output_vio == NULL)) {
      INKError("INKVConnWrite returns NULL or INK_ERROR_PTR");
    }
  }
  return 1;
}

static int
transform_buffer_event(INKCont contp, TransformData * data, INKEvent event, void *edata)
{
  INKVIO write_vio;
  int towrite;
  int avail;

  if (!data->input_buf) {
    INKIOBufferData d;
    INKIOBufferBlock b;

    data->input_buf = INKIOBufferCreate();
    if ((data->input_buf == NULL) || (data->input_buf == INK_ERROR_PTR)) {
      INKError("Error in Creating buffer");
      return -1;
    }
    data->input_reader = INKIOBufferReaderAlloc(data->input_buf);
    if ((data->input_reader == NULL) || (data->input_reader == INK_ERROR_PTR)) {
      INKError("Unable to allocate a reader to input buffer.");
      return -1;
    }
  }

  /* 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 = INKVConnWriteVIOGet(contp);
  if (write_vio == INK_ERROR_PTR) {
    INKError("Corrupted write VIO received.");
  }

  /* 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 (!INKVIOBufferGet(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 = INKVIONTodoGet(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 = INKIOBufferReaderAvail(INKVIOReaderGet(write_vio));
    if (avail == INK_ERROR) {
      INKError("Unable to get the number of bytes availabe for reading");
    } else {
      if (towrite > avail) {
        towrite = avail;
      }

      if (towrite > 0) {
        /* Copy the data from the read buffer to the input buffer. */
        if (INKIOBufferCopy(data->input_buf, INKVIOReaderGet(write_vio), towrite, 0) == INK_ERROR) {
          INKError("Error in Copying the buffer");
        } else {

          /* Tell the read buffer that we have read the data and are no
             longer interested in it. */
          if (INKIOBufferReaderConsume(INKVIOReaderGet(write_vio), towrite) != INK_SUCCESS) {
            INKError("Unable to consume bytes from the buffer");
          }

          /* Modify the write VIO to reflect how much data we've
             completed. */
          if (INKVIONDoneSet(write_vio, INKVIONDoneGet(write_vio) + towrite) != INK_SUCCESS) {
            INKError("Unable to modify the write VIO to reflect how much data we have completed");
          }
        }
      }
    }
  } else {
    if (towrite == INK_ERROR) {
      INKError("INKVIONTodoGet returns INK_ERROR");
      return 0;
    }
  }

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

    /* start compression... */
    return transform_connect(contp, data);
  }

  return 0;
}

static int
transform_connect_event(INKCont contp, TransformData * data, INKEvent event, void *edata)
{
  switch (event) {
  case INK_EVENT_NET_CONNECT:
    data->pending_action = NULL;
    data->server_vc = (INKVConn) edata;
    return transform_write(contp, data);
  case INK_EVENT_NET_CONNECT_FAILED:
    data->pending_action = NULL;
    return transform_bypass(contp, data);
  default:
    break;
  }

  return 0;
}

static int
transform_write_event(INKCont contp, TransformData * data, INKEvent event, void *edata)
{
  switch (event) {
  case INK_EVENT_VCONN_WRITE_READY:
    if (INKVIOReenable(data->server_vio) != INK_SUCCESS) {
      INKError("Unable to reenable the server vio in INK_EVENT_VCONN_WRITE_READY");
    }
    break;
  case INK_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(INKCont contp, TransformData * data, INKEvent event, void *edata)
{
  switch (event) {
  case INK_EVENT_ERROR:
  case INK_EVENT_VCONN_EOS:
    return transform_bypass(contp, data);
  case INK_EVENT_VCONN_READ_COMPLETE:
    if (INKIOBufferReaderAvail(data->output_reader) == sizeof(int)) {
      INKIOBufferBlock blk;
      char *buf;
      void *buf_ptr;
      int avail;
      int read_nbytes = sizeof(int);
      int read_ndone = 0;

      buf_ptr = &data->content_length;
      while (read_nbytes > 0) {
        blk = INKIOBufferReaderStart(data->output_reader);
        if (blk == INK_ERROR_PTR) {
          INKError("Error in Getting the pointer to starting of reader block");
        } else {
          buf = (char *) INKIOBufferBlockReadStart(blk, data->output_reader, &avail);
          if (buf != INK_ERROR_PTR) {
            read_ndone = (avail >= read_nbytes) ? read_nbytes : avail;
            memcpy(buf_ptr, buf, read_ndone);
            if (read_ndone > 0) {
              if (INKIOBufferReaderConsume(data->output_reader, read_ndone) != INK_SUCCESS) {
                INKError("Error in consuming data from the buffer");
              } else {
                read_nbytes -= read_ndone;
                /* move ptr frwd by read_ndone bytes */
                buf_ptr = (char *) buf_ptr + read_ndone;
              }
            }
          } else {
            INKError("INKIOBufferBlockReadStart returns INK_ERROR_PTR");
          }
        }
      }
      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(INKCont contp, TransformData * data, INKEvent event, void *edata)
{
  switch (event) {
  case INK_EVENT_ERROR:
    if (INKVConnAbort(data->server_vc, 1) != INK_SUCCESS) {
      INKError("INKVConnAbort doesn't return INK_SUCCESS on server VConnection during INK_EVENT_ERROR");
    }
    data->server_vc = NULL;
    data->server_vio = NULL;

    if (INKVConnAbort(data->output_vc, 1) != INK_SUCCESS) {
      INKError("INKVConnAbort doesn't return INK_SUCCESS on output VConnection during INK_EVENT_ERROR");
    }
    data->output_vc = NULL;
    data->output_vio = NULL;
    break;
  case INK_EVENT_VCONN_EOS:
    if (INKVConnAbort(data->server_vc, 1) != INK_SUCCESS) {
      INKError("INKVConnAbort doesn't return INK_SUCCESS on server VConnection during INK_EVENT_VCONN_EOS");
    }
    data->server_vc = NULL;
    data->server_vio = NULL;

    if (INKVConnAbort(data->output_vc, 1) != INK_SUCCESS) {
      INKError("INKVConnAbort doesn't return INK_SUCCESS on output VConnection during INK_EVENT_VCONN_EOS");
    }
    data->output_vc = NULL;
    data->output_vio = NULL;
    break;
  case INK_EVENT_VCONN_READ_COMPLETE:
    if (INKVConnClose(data->server_vc) != INK_SUCCESS) {
      INKError("INKVConnClose doesn't return INK_SUCCESS on INK_EVENT_VCONN_READ_COMPLETE");
    }
    data->server_vc = NULL;
    data->server_vio = NULL;

    if (INKVIOReenable(data->output_vio) != INK_SUCCESS) {
      INKError("INKVIOReneable doesn't return INK_SUCCESS on INK_EVENT_VCONN_READ_COMPLETE");
    }
    break;
  case INK_EVENT_VCONN_READ_READY:
    if (INKVIOReenable(data->output_vio) != INK_SUCCESS) {
      INKError("INKVIOReneable doesn't return INK_SUCCESS on INK_EVENT_VCONN_READ_READY");
    }
    break;
  case INK_EVENT_VCONN_WRITE_COMPLETE:
    if (INKVConnShutdown(data->output_vc, 0, 1) != INK_SUCCESS) {
      INKError("INKVConnShutdown doesn't return INK_SUCCESS during INK_EVENT_VCONN_WRITE_COMPLETE");
    }
    break;
  case INK_EVENT_VCONN_WRITE_READY:
    if (INKVIOReenable(data->server_vio) != INK_SUCCESS) {
      INKError("INKVIOReneable doesn't return INK_SUCCESS while reenabling on INK_EVENT_VCONN_WRITE_READY");
    }
    break;
  default:
    break;
  }

  return 0;
}

static int
transform_bypass_event(INKCont contp, TransformData * data, INKEvent event, void *edata)
{
  switch (event) {
  case INK_EVENT_VCONN_WRITE_COMPLETE:
    if (INKVConnShutdown(data->output_vc, 0, 1) != INK_SUCCESS) {
      INKError("Error in shutting down the VConnection while bypassing the event");
    }
    break;
  case INK_EVENT_VCONN_WRITE_READY:
  default:
    if (INKVIOReenable(data->output_vio) != INK_SUCCESS) {
      INKError("Error in re-enabling the VIO while bypassing the event");
    }
    break;
  }

  return 0;
}

static int
transform_handler(INKCont contp, INKEvent event, void *edata)
{
  /* Check to see if the transformation has been closed by a call to
     INKVConnClose. */
  if (INKVConnClosedGet(contp)) {
    transform_destroy(contp);
    return 0;
  } else {
    TransformData *data;
    int val;

    data = (TransformData *) INKContDataGet(contp);
    if ((data == NULL) && (data == INK_ERROR_PTR)) {
      INKError("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(INKHttpTxn 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(INKHttpTxn 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(INKHttpTxn 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.  
   */

  INKMBuffer bufp;
  INKMLoc hdr_loc;
  INKHttpStatus resp_status;

  if (INKHttpTxnServerRespGet(txnp, &bufp, &hdr_loc) == 0) {
    INKError("Unable to get handle to Server Response");
    return 0;
  }

  if ((resp_status = INKHttpHdrStatusGet(bufp, hdr_loc)) == INK_ERROR) {
    INKError("Error in Getting Status from Server response");
    if (INKHandleMLocRelease(bufp, INK_NULL_MLOC, hdr_loc) != INK_SUCCESS) {
      INKError("Unable to release handle to server request");
    }
    return 0;
  }

  if (INK_HTTP_STATUS_OK == resp_status) {
    if (INKHandleMLocRelease(bufp, INK_NULL_MLOC, hdr_loc) != INK_SUCCESS) {
      INKError("Unable to release handle to server request");
    }
    return 1;
  } else {
    if (INKHandleMLocRelease(bufp, INK_NULL_MLOC, hdr_loc) != INK_SUCCESS) {
      INKError("Unable to release handle to server request");
    }
    return 0;
  }
}

static int
transform_plugin(INKCont contp, INKEvent event, void *edata)
{
  INKHttpTxn txnp = (INKHttpTxn) edata;

  switch (event) {
  case INK_EVENT_HTTP_READ_REQUEST_HDR:
    if (request_ok(txnp)) {
      if (INKHttpTxnHookAdd(txnp, INK_HTTP_READ_CACHE_HDR_HOOK, contp) != INK_SUCCESS) {
        INKError("Unable to add continuation to hook " "INK_HTTP_READ_CACHE_HDR_HOOK for this transaction");
      }
      if (INKHttpTxnHookAdd(txnp, INK_HTTP_READ_RESPONSE_HDR_HOOK, contp) != INK_SUCCESS) {
        INKError("Unable to add continuation to hook " "INK_HTTP_READ_RESPONSE_HDR_HOOK for this transaction");
      }
    }
    if (INKHttpTxnReenable(txnp, INK_EVENT_HTTP_CONTINUE) != INK_SUCCESS) {
      INKError("Error in re-enabling transaction at INK_HTTP_READ_REQUEST_HDR_HOOK");
    }
    break;
  case INK_EVENT_HTTP_READ_CACHE_HDR:
    if (cache_response_ok(txnp)) {
      if (INKHttpTxnHookAdd(txnp, INK_HTTP_RESPONSE_TRANSFORM_HOOK, transform_create(txnp)) != INK_SUCCESS) {
        INKError("Unable to add continuation to tranformation hook " "for this transaction");
      }
    }
    if (INKHttpTxnReenable(txnp, INK_EVENT_HTTP_CONTINUE) != INK_SUCCESS) {
      INKError("Error in re-enabling transaction at INK_HTTP_READ_CACHE_HDR_HOOK");
    }
    break;
  case INK_EVENT_HTTP_READ_RESPONSE_HDR:
    if (server_response_ok(txnp)) {
      if (INKHttpTxnHookAdd(txnp, INK_HTTP_RESPONSE_TRANSFORM_HOOK, transform_create(txnp)) != INK_SUCCESS) {
        INKError("Unable to add continuation to tranformation hook " "for this transaction");
      }
    }
    if (INKHttpTxnReenable(txnp, INK_EVENT_HTTP_CONTINUE) != INK_SUCCESS) {
      INKError("Error in re-enabling transaction at INK_HTTP_READ_RESPONSE_HDR_HOOK");
    }
    break;
  default:
    break;
  }
  return 0;
}

int
check_ts_version()
{

  const char *ts_version = INKTrafficServerVersionGet();
  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 5.2 plugin, we need at
       least Traffic Server 5.2.0 to run */
    if (major_ts_version > 5) {
      result = 1;
    } else if (major_ts_version == 5) {
      if (minor_ts_version > 2) {
        result = 1;
      } else if (minor_ts_version == 2) {
        if (patch_ts_version >= 0) {
          result = 1;
        }
      }
    }
  }

  return result;
}

void
INKPluginInit(int argc, const char *argv[])
{
  INKPluginRegistrationInfo info;
  INKCont cont;

  info.plugin_name = "server-transform";
  info.vendor_name = "MyCompany";
  info.support_email = "ts-api-support@MyCompany.com";

  if (!INKPluginRegister(INK_SDK_VERSION_5_2, &info)) {
    INKError("Plugin registration failed.\n");
  }

  if (!check_ts_version()) {
    INKError("Plugin requires Traffic Server 5.2.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;

  if ((cont = INKContCreate(transform_plugin, NULL)) == INK_ERROR_PTR) {
    INKError("Unable to create continuation. Aborting...");
    return;
  }

  if (INKHttpHookAdd(INK_HTTP_READ_REQUEST_HDR_HOOK, cont) == INK_ERROR) {
    INKError("Unable to add the continuation to the hook. Aborting...");
    if (INKContDestroy(cont) == INK_ERROR) {
      INKError("Error in Destroying the continuation.");
    }
  }
}
