/*-
 ***********************************************************************
 *
 * $Id: hook.c,v 1.15 2012/01/07 07:56:14 mavrik Exp $
 *
 ***********************************************************************
 *
 * Copyright 2003-2012 The WebJob Project, All Rights Reserved.
 *
 ***********************************************************************
 */
#include "all-includes.h"

/*-
 ***********************************************************************
 *
 * HookCalculateCommandLineLength
 *
 ***********************************************************************
 */
int
HookCalculateCommandLineLength(HOOK *psHook)
{
  char               *apcTokens[HOOK_MAX_TOKEN_COUNT];
  char               *pc;
  char               *pcCommand;
  int                 i;
  int                 iIndex;
  int                 iLength;
  int                 iEscapeCount;
  int                 iTokensCount[HOOK_MAX_TOKEN_COUNT];

  apcTokens[0] = psHook->pcModifiedFilenameToken;
  apcTokens[1] = psHook->pcOriginalFilenameToken;
  apcTokens[2] = psHook->pcPidToken;
  apcTokens[3] = psHook->pcSuffixToken;

  iTokensCount[0] = 0;
  iTokensCount[1] = 0;
  iTokensCount[2] = 0;
  iTokensCount[3] = 0;

  for (i = iEscapeCount = 0, pcCommand = psHook->pcOriginalCommandLine; i < HOOK_MAX_TOKEN_COUNT; i++)
  {
    iLength = strlen(apcTokens[i]);
    for (iIndex = 0; apcTokens[i][0] != 0 && (pc = strstr(&pcCommand[iIndex], apcTokens[i])) != NULL; iIndex += iLength)
    {
      iIndex += pc - &pcCommand[iIndex];
      if (iIndex > 0)
      {
        if (*(pc - 1) == HOOK_TOKEN_PREFIX_C)
        {
          iEscapeCount++;
        }
        else
        {
          iTokensCount[i]++;
        }
      }
      else
      {
        iTokensCount[i]++;
      }
    }
  }

  iLength = strlen(psHook->pcOriginalCommandLine) - (strlen(HOOK_TOKEN_PREFIX_S) * iEscapeCount) +
    ((strlen(psHook->pcModifiedFilename) - strlen(psHook->pcModifiedFilenameToken)) * iTokensCount[0]) +
    ((strlen(psHook->pcOriginalFilename) - strlen(psHook->pcOriginalFilenameToken)) * iTokensCount[1]) +
    ((strlen(psHook->pcPid) - strlen(psHook->pcPidToken)) * iTokensCount[2]) +
    ((strlen(psHook->pcSuffix) - strlen(psHook->pcSuffixToken)) * iTokensCount[3]);

  return (iLength < 0) ? 0 : iLength;
}


/*-
 ***********************************************************************
 *
 * HookExpandCommandLine
 *
 ***********************************************************************
 */
char *
HookExpandCommandLine(HOOK *psHook, char *pcError)
{
  const char          acRoutine[] = "HookExpandCommandLine()";
  char               *pc;
  char               *pcExpanded;
  char               *pcOriginal;
  int                 iLength;

  pcExpanded = pc = malloc(HookCalculateCommandLineLength(psHook) + 1);
  if (pcExpanded == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: malloc(): %s", acRoutine, strerror(errno));
    return NULL;
  }

  for (pcOriginal = psHook->pcOriginalCommandLine; *pcOriginal; pcOriginal++)
  {
    if (*pcOriginal == HOOK_TOKEN_PREFIX_C)
    {
      switch (*(pcOriginal + 1))
      {
      case 0:
        snprintf(pcError, MESSAGE_SIZE, "%s: Commands must not end with an unescaped '%c'.", acRoutine, HOOK_TOKEN_PREFIX_C);
        free(pcExpanded);
        return NULL;
        break;
      case HOOK_TOKEN_PREFIX_C:
        *pc++ = *pcOriginal++;
        *pc++ = *pcOriginal;
        break;
      default:
        if (strncmp(pcOriginal, psHook->pcModifiedFilenameToken, strlen(psHook->pcModifiedFilenameToken)) == 0)
        {
          iLength = strlen(psHook->pcModifiedFilename);
          strncpy(pc, psHook->pcModifiedFilename, iLength + 1);
          pc += iLength;
          pcOriginal += strlen(psHook->pcModifiedFilenameToken) - 1;
        }
        else if (strncmp(pcOriginal, psHook->pcOriginalFilenameToken, strlen(psHook->pcOriginalFilenameToken)) == 0)
        {
          iLength = strlen(psHook->pcOriginalFilename);
          strncpy(pc, psHook->pcOriginalFilename, iLength + 1);
          pc += iLength;
          pcOriginal += strlen(psHook->pcOriginalFilenameToken) - 1;
        }
        else if (strncmp(pcOriginal, HOOK_PID_TOKEN, strlen(HOOK_PID_TOKEN)) == 0)
        {
          iLength = strlen(psHook->pcPid);
          strncpy(pc, psHook->pcPid, iLength + 1);
          pc += iLength;
          pcOriginal += strlen(psHook->pcPidToken) - 1;
        }
        else if (strncmp(pcOriginal, psHook->pcSuffixToken, strlen(psHook->pcSuffixToken)) == 0)
        {
          iLength = strlen(psHook->pcSuffix);
          strncpy(pc, psHook->pcSuffix, iLength + 1);
          pc += iLength;
          pcOriginal += strlen(psHook->pcSuffixToken) - 1;
        }
        else
        {
          *pc++ = *pcOriginal;
        }
        break;
      }
    }
    else
    {
      *pc++ = *pcOriginal;
    }
  }
  *pc = 0;

  return pcExpanded;
}


/*-
 ***********************************************************************
 *
 * HookFreeHook
 *
 ***********************************************************************
 */
void
HookFreeHook(HOOK *psHook)
{
  if (psHook != NULL)
  {
    if (psHook->pcExpandedCommandLine != NULL)
    {
      free(psHook->pcExpandedCommandLine);
    }
    if (psHook->pcOriginalCommandLine != NULL)
    {
      free(psHook->pcOriginalCommandLine);
    }
    if (psHook->pcModifiedFilename != NULL)
    {
      free(psHook->pcModifiedFilename);
    }
    if (psHook->pcModifiedFilenameToken != NULL)
    {
      free(psHook->pcModifiedFilenameToken);
    }
    if (psHook->pcOriginalFilename != NULL)
    {
      free(psHook->pcOriginalFilename);
    }
    if (psHook->pcOriginalFilenameToken != NULL)
    {
      free(psHook->pcOriginalFilenameToken);
    }
    if (psHook->pcPid != NULL)
    {
      free(psHook->pcPid);
    }
    if (psHook->pcPidToken != NULL)
    {
      free(psHook->pcPidToken);
    }
    if (psHook->pcSuffix != NULL)
    {
      free(psHook->pcSuffix);
    }
    if (psHook->pcSuffixToken != NULL)
    {
      free(psHook->pcSuffixToken);
    }
    free(psHook);
  }
}


/*-
 ***********************************************************************
 *
 * HookNewHook
 *
 ***********************************************************************
 */
HOOK *
HookNewHook(char *pcError)
{
  const char          acRoutine[] = "HookNewHook()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char                acPid[HOOK_PID_SIZE] = { 0 };
  int                 iError;
  HOOK               *psHook;

  psHook = (HOOK *) calloc(sizeof(HOOK), 1);
  if (psHook == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return NULL;
  }

  iError = HookSetValue(psHook, HOOK_VALUE_EXPANDED_COMMAND_LINE, "", acLocalError);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    HookFreeHook(psHook);
    return NULL;
  }

  iError = HookSetValue(psHook, HOOK_VALUE_ORIGINAL_COMMAND_LINE, "", acLocalError);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    HookFreeHook(psHook);
    return NULL;
  }

  iError = HookSetValue(psHook, HOOK_VALUE_MODIFIED_FILENAME, "", acLocalError);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    HookFreeHook(psHook);
    return NULL;
  }

  iError = HookSetToken(psHook, HOOK_VALUE_MODIFIED_FILENAME_TOKEN, HOOK_MODIFIED_FILENAME_TOKEN, acLocalError);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    HookFreeHook(psHook);
    return NULL;
  }

  iError = HookSetValue(psHook, HOOK_VALUE_ORIGINAL_FILENAME, "", acLocalError);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    HookFreeHook(psHook);
    return NULL;
  }

  iError = HookSetToken(psHook, HOOK_VALUE_ORIGINAL_FILENAME_TOKEN, HOOK_ORIGINAL_FILENAME_TOKEN, acLocalError);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    HookFreeHook(psHook);
    return NULL;
  }

  snprintf(acPid, HOOK_PID_SIZE, "%d", (int) getpid());

  iError = HookSetValue(psHook, HOOK_VALUE_PID, acPid, acLocalError);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    HookFreeHook(psHook);
    return NULL;
  }

  iError = HookSetToken(psHook, HOOK_VALUE_PID_TOKEN, HOOK_PID_TOKEN, acLocalError);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    HookFreeHook(psHook);
    return NULL;
  }

  iError = HookSetValue(psHook, HOOK_VALUE_SUFFIX, "", acLocalError);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    HookFreeHook(psHook);
    return NULL;
  }

  iError = HookSetToken(psHook, HOOK_VALUE_SUFFIX_TOKEN, HOOK_SUFFIX_TOKEN, acLocalError);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    HookFreeHook(psHook);
    return NULL;
  }

  psHook->iActualStatus = HOOK_DEFAULT_STATUS;

  psHook->iTargetStatus = HOOK_DEFAULT_STATUS;

  return psHook;
}


/*-
 ***********************************************************************
 *
 * HookRunSystemCommand
 *
 ***********************************************************************
 */
int
HookRunSystemCommand(HOOK *psHook, char *pcError)
{
  const char          acRoutine[] = "HookRunSystemCommand()";
  int                 iStatus;

  if (psHook->pcExpandedCommandLine == NULL || psHook->pcExpandedCommandLine[0] == 0)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Command line must not be NULL.", acRoutine);
    return -1;
  }

  iStatus = system(psHook->pcExpandedCommandLine);
  if (iStatus == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: system(): %s", acRoutine, strerror(errno));
    unlink(psHook->pcModifiedFilename);
    return -1;
  }
#ifdef WIN32
  psHook->iActualStatus = iStatus;
#else
  psHook->iActualStatus = WEXITSTATUS(iStatus);
#endif

  if (psHook->iActualStatus != psHook->iTargetStatus)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: ActualStatus = [%d] != [%d]: ActualStatus vs TargetStatus Mismatch.",
      acRoutine,
      psHook->iActualStatus,
      psHook->iTargetStatus
      );
    return -1;
  }

  return (psHook->iActualStatus == psHook->iTargetStatus) ? 0 : -1;
}


/*-
 ***********************************************************************
 *
 * HookSetDynamicString
 *
 ***********************************************************************
 */
int
HookSetDynamicString(char **ppcValue, char *pcNewValue, char *pcError)
{
  const char          acRoutine[] = "HookSetDynamicString()";
  char               *pcTempValue;
  int                 iLength;

  iLength = strlen(pcNewValue);
  pcTempValue = realloc(*ppcValue, iLength + 1);
  {
    if (pcTempValue == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: realloc(): %s", acRoutine, strerror(errno));
      return -1;
    }
    *ppcValue = pcTempValue;
  }
  strncpy(*ppcValue, pcNewValue, iLength + 1);

  return 0;
}


/*-
 ***********************************************************************
 *
 * HookSetToken
 *
 ***********************************************************************
 */
int
HookSetToken(HOOK *psHook, int iTokenType, char *pcNewValue, char *pcError)
{
  const char          acRoutine[] = "HookSetToken()";
  char                acLocalError[MESSAGE_SIZE] = "";
  int                 i;
  int                 iError;

  if (psHook == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Undefined Hook.", acRoutine);
    return -1;
  }

  if (pcNewValue[0] != HOOK_TOKEN_PREFIX_C)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Tokens must be prefixed with '%c'.", acRoutine, HOOK_TOKEN_PREFIX_C);
    return -1;
  }

  for (i = 1; i < (int) strlen(pcNewValue); i++)
  {
    if (pcNewValue[i] == HOOK_TOKEN_PREFIX_C)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Tokens must not contain embedded '%c's.", acRoutine, HOOK_TOKEN_PREFIX_C);
      return -1;
    }
  }

  switch (iTokenType)
  {
  case HOOK_VALUE_MODIFIED_FILENAME_TOKEN:
    iError = HookSetDynamicString(&psHook->pcModifiedFilenameToken, pcNewValue, acLocalError);
    break;
  case HOOK_VALUE_ORIGINAL_FILENAME_TOKEN:
    iError = HookSetDynamicString(&psHook->pcOriginalFilenameToken, pcNewValue, acLocalError);
    break;
  case HOOK_VALUE_PID_TOKEN:
    iError = HookSetDynamicString(&psHook->pcPidToken, pcNewValue, acLocalError);
    break;
  case HOOK_VALUE_SUFFIX_TOKEN:
    iError = HookSetDynamicString(&psHook->pcSuffixToken, pcNewValue, acLocalError);
    break;
  default:
    snprintf(pcError, MESSAGE_SIZE, "%s: An invalid token type (%d) was specified -- this should not happen.", acRoutine, iTokenType);
    return -1;
    break;
  }
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return -1;
  }

  return 0;
}


/*-
 ***********************************************************************
 *
 * HookSetValue
 *
 ***********************************************************************
 */
int
HookSetValue(HOOK *psHook, int iValueType, char *pcNewValue, char *pcError)
{
  const char          acRoutine[] = "HookSetValue()";
  char                acLocalError[MESSAGE_SIZE] = "";
  int                 iError;

  if (psHook == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Undefined Hook.", acRoutine);
    return -1;
  }

  switch (iValueType)
  {
  case HOOK_VALUE_EXPANDED_COMMAND_LINE:
    iError = HookSetDynamicString(&psHook->pcExpandedCommandLine, pcNewValue, acLocalError);
    break;
  case HOOK_VALUE_ORIGINAL_COMMAND_LINE:
    iError = HookSetDynamicString(&psHook->pcOriginalCommandLine, pcNewValue, acLocalError);
    break;
  case HOOK_VALUE_MODIFIED_FILENAME:
    iError = HookSetDynamicString(&psHook->pcModifiedFilename, pcNewValue, acLocalError);
    break;
  case HOOK_VALUE_ORIGINAL_FILENAME:
    iError = HookSetDynamicString(&psHook->pcOriginalFilename, pcNewValue, acLocalError);
    break;
  case HOOK_VALUE_PID:
    iError = HookSetDynamicString(&psHook->pcPid, pcNewValue, acLocalError);
    break;
  case HOOK_VALUE_SUFFIX:
    iError = HookSetDynamicString(&psHook->pcSuffix, pcNewValue, acLocalError);
    break;
  default:
    snprintf(pcError, MESSAGE_SIZE, "%s: An invalid value type (%d) was specified -- this should not happen.", acRoutine, iValueType);
    return -1;
    break;
  }
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return -1;
  }

  return 0;
}
