/* DropFiles: a Dekkerware console application.

DropFiles.exe drops files onto a window.
For example, the following command will drop a file named "setuplog.txt"
onto the specified window handle 000105FE:
  DropFiles.exe 000105FE C:\WINDOWS\setuplog.txt
For help, run DropFiles.exe without arguments.
Or check out the website:
  http://www.xs4all.nl/~nd/dekkerware/console/index.htm

Compiled by MSVC++ 5,  MSVC++ 6, and MSVC++ .NET 2003:
  CL.EXE /D "and=&&" /D "or=||" /D "not=!" /W4 DropFiles.cpp /link user32.lib
Compiled by g++ (GCC) 3.3.1 (cygming special):
  GCC.EXE DropFiles.cpp -Wall -oDropFiles.exe
Compiled by Borland C++ 5.5.1:
  BCC32.EXE -D"and=&&" -D"or=||" -D"not=!" -w -IInclude -LLib DropFiles.cpp
Tested on Windows 98 and Windows XP SP1.

Author: Niels Dekker
E-mail: "dekkerware" AT "xs4all" DOT "nl"
***********************************************************************
Update History:
2003-11-30  version 1.0
  Released!
2003-12-03  version 1.1
  ExtractFileName function added.
  Check on user input 'q' optimized, using tolower().
  64-bit portability issues on the use of HWND clarified.
  "/// " replaced by "// " to get a nicer "Code Comment Web Report".
2003-12-06  version 1.2
  Memory leak in DropArgumentsOnWindow fixed by calling GlobalFree.
  Commandline flag /help supported.
  Checked for each argument if it is a NULL-pointer.
2003-12-19  version 1.3
  Compiled by GNU g++ (GCC) 3.3.1
  Non-ANSI function stricmp replaced by handwritten SameText.
  Format string in call to sprintf in WindowToHex corrected, due to
  GCC warning "unsigned int format, long unsigned int arg (arg 3)".
  Removed shadowing declarations of the integers i and ch in main.
2003-12-24  version 1.4
  Standard C/C++ macro's EXIT_SUCCESS and EXIT_FAILURE used.
  Moved from ~nd/dekkerware/dropfiles/ to ~nd/dekkerware/console/
  Potential crashes on NULL-pointer avoided in ExtractFileName.
  Now asks only once for handle of target window from standard input.
  WindowToHex function removed.  Source file untabified.
2003-12-26  version 1.5
  No more slash in front on the first command line argument!
  No more "/?" or "/help" for help!
  I now use the term "files" instead of "arguments to drop".
  The program now asks for files if they were not on the command line.
2004-01-01  version 1.6
  Non-Standard SIZE_T replaced by size_t for MSVC++ 5.0 compatibility.
  'u' used as prefix for variables of size_t (Hongarian notation).
  signed/unsigned-correction: "int i" replaced by "size_t uNumChars".
  Forgotten return EXIT_FAILURE added.
  ExtractFileName now treats forward slashes just like backslashes.
  Compiled using Borland C++ 5.5.1.
2004-01-04  version 1.7
  The filename of application is now printed in uppercase.
  In ExtractFileName: int replaced by size_t to avoid "loss of data".
2004-02-21  version 1.8
  Typo fix in help output (when running the program without arguments).
  "printf" calls replaced by simpler "puts" calls where appropriate,
  thereby removing abundant carriage return characters from the output.
  Output flushed when asking for input from Standard I/0.
  (I ignore the return values of both puts and fflush, which is common
  practice for printf as well.)
  Improved (clarified) the system error message at standard output.
  Fixed support for zero files to drop that was removed at version 1.5.
  Arguments of the main function argc and argv treated more carefully.
2004-05-03  version 1.9
  Fixed the memory leaks that I used to ignore.
2004-05-19  version 1.10
  Overflow checks added to implementation of DropFiles function.
  "int" replaced by "unsigned int" wherever appropriate.
***********************************************************************
*/

// Including toupper:
#include <ctype.h>
// Including fflush, printf, and puts:
#include <stdio.h>
// Including strtoul, EXIT_SUCCESS, and EXIT_FAILURE:
#include <stdlib.h>
// Including strncpy:
#include <string.h>

// Including DROPFILES:
#include <shlobj.h>
// Including HWND:
#include <windows.h>

#ifndef __cplusplus
#ifndef __bool_true_false_are_defined
// C++ style Booleans for C, simular to the C header <stdbool.h>:
#define bool int
#define true (1==1)
#define false (0!=0)
#endif
// Alternative spellings taken from the C header <iso646.h>:
#ifndef and
#define and &&
#endif
#ifndef not
#define not !
#endif
#ifndef or
#define or ||
#endif
#endif

// <summary>
// Drops files onto a window, by posting a DROPFILES message.
// </summary>
// <param name="hWindow">
// Handle to the target window.
// Two values have special meanings: HWND_BROADCAST (ffff) and NULL (0).
// </param>
// <param name="ppszFiles">
// The files: an array of zero terminated strings.
// </param>
// <param name="iNumFiles">
// The number of files.  Must be greater than zero.
// </param>
// <returns>
// True iff the files have been posted to the window sucessfully.
// </returns>
bool DropFiles(
  const HWND hWindow,
  const char * const ppszFiles[],
  const unsigned int uNumFiles)
{
  if ( (uNumFiles == 0) or (ppszFiles != NULL) )
  {
    size_t uNumBytes = sizeof(DROPFILES) + 1;
    unsigned int uIndex = uNumFiles;
    HGLOBAL hGlob;

    while ( uIndex > 0 )
    {
      const char * const pszFile = ppszFiles[--uIndex];

      if ( pszFile != NULL )
      {
        const size_t uOldNumBytes = uNumBytes;
        
        uNumBytes += strlen(pszFile); 
        
        if ( (uNumBytes < uOldNumBytes) or ((uNumBytes + 1) <= uNumBytes) )
        {
          // Overflow!
          return false;
        }
        else
        {
          // One more byte for the zero terminator character.
          ++uNumBytes;
        }
      }
    }
    hGlob = GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE | GMEM_ZEROINIT, uNumBytes);
    if ( hGlob != NULL )
    {
      DROPFILES * const pDrop = (DROPFILES *)GlobalLock(hGlob);

      if ( pDrop != NULL )
      {
        char * pc = (char *)pDrop + sizeof(DROPFILES);

        pDrop->pFiles = sizeof(DROPFILES);

        for ( uIndex = 0; uIndex < uNumFiles; ++uIndex )
        {
          const char * const pszFile = ppszFiles[uIndex];

          if ( pszFile != NULL )
          {
            strcpy(pc, pszFile);
            pc += strlen(pszFile) + 1;
          }
        }
        GlobalUnlock(hGlob);

        if ( PostMessage(hWindow, WM_DROPFILES, (WPARAM)hGlob, 0) )
        {
          return true;
        }
      }
      GlobalFree(hGlob);
    }
  }
  return false;
}


// <summary>
// Converts a hexadecimal string to the window handle it represents.
// </summary>
// <remarks>
// If the hexadecimal string represents a number greater than ULONG_MAX,
// the resulting window handle will be incorrect.
// </remarks>
// <param name="pszHex">
// Hexadecimal number, passed as a zero-terminated string.
// (No "0x"-prefix, please!) 
// </param>
// <param name="phWindow">
// Pointer to a buffer to store the resulting window handle.
// </param>
// <returns>
// True iff sucessful.
// </returns>
bool HexToWindow(const char pszHex[], HWND * const phWindow)
{
  if ( (pszHex == NULL) or (phWindow == NULL) )
  {
    return false;
  }
  else
  {
    char * pszTail = NULL;

    // MSVC++ 2003 compiler has detected a 64-bit portability issue,
    // warning C4312: 'type cast' : conversion from 'unsigned long' to
    // 'HWND' of greater size
    *phWindow = (HWND)strtoul(pszHex, &pszTail, 16);

    return (pszTail != NULL) and (*pszTail == '\0');
  }
}


// Frees the strings from the specified array, and the array itself.
void FreeArrayOfStrings(
  const char * const ppszArray[], 
  unsigned int uNumStrings)
{
  if ( ppszArray != NULL )
  {
    while( uNumStrings > 0 )
    {
      free( (void *)ppszArray[--uNumStrings] );
    }
    free( (void *)ppszArray );
  }
}


// <summary>
// Reads a line from Standard Input.
// </summary>
// <returns>
// A buffer that contains the line, excluding end-of-line characters
// or NULL in case of a memory failure.
// </returns>
const char * ReadLine(void)
{
  char * pszResult = NULL;
  size_t uSize = 0;
  int iChar = getchar();
  
  while ( (iChar != EOF) and (iChar != '\n') )
  {
    if ( (uSize + 1) > uSize )
    {
      void * const pvOldResult = pszResult;

      if ( (pszResult = (char *)realloc(pszResult, ++uSize)) == NULL )
      {
        free(pvOldResult);
        return NULL;
      }
      else
      {
        pszResult[uSize - 1] = (char)iChar;
        iChar = getchar();
      }
    }
    else
    {
      // Overflow of uSize.
      free(pszResult);
      return NULL;
    }
  }
  if ( (pszResult == NULL) or (pszResult[uSize - 1] != '\r') )
  {
    if ( (uSize + 1) > uSize )
    {
      void * const pvOldResult = pszResult;

      if ( (pszResult = (char *)realloc(pszResult, ++uSize)) == NULL )
      {
        free(pvOldResult);
        return NULL;
      }
    }
    else
    {
      // Overflow of uSize.
      free(pszResult);
      return NULL;
    }
  }
  pszResult[uSize - 1] = '\0';
  return pszResult;
}


const char * const * AskForFiles(unsigned int *const puNumFiles)
{
  const char * * ppszResult = NULL;
  unsigned int uNumStrings = 0;

  for(;;)
  {
    const char * pszInputLine;

    printf("File %u: ", uNumStrings + 1);
    fflush(stdout);

    if ( (pszInputLine = ReadLine()) == NULL )
    {
      FreeArrayOfStrings(ppszResult, uNumStrings);
      ppszResult = NULL;
      break;
    }
    else
    {
      if ( pszInputLine[0] == '\0' )
      {
        free( (void *)pszInputLine );
        break;
      }
      else
      {
        if ( uNumStrings + 1 > uNumStrings )
        {
          const char * const * const ppszOldResult = ppszResult;
          ppszResult = (const char * *)realloc(ppszResult, ++uNumStrings * sizeof(char *));

          if ( ppszResult == NULL )
          {
            free( (void *)pszInputLine );
            FreeArrayOfStrings(ppszOldResult, uNumStrings - 1);
            ppszResult = NULL;
            break;
          }
          else
          {
            ppszResult[uNumStrings - 1] = pszInputLine;
          }
        }
        else
        {
          // Overflow of uNumStrings!!!
          free( (void *)pszInputLine );
          FreeArrayOfStrings(ppszResult, uNumStrings);
          ppszResult = NULL;
          break;
        }
      }
    }
  }
  *puNumFiles = uNumStrings;
  return ppszResult;
}


// <summary>
// Extracts the name and extension parts of a file name.
// (Inspired by Borland Delphi Visual Component Library.)
// </summary>
// <param name="pszFile">
// A relative file name or a full file path.
// </param>
// <returns>
// Pointer to the start of the file name part.
// </returns>
const char * ExtractFileName(const char pszFile[])
{
  if ( pszFile == NULL )
  {
    return "";
  }
  else
  {
    size_t uIndex = strlen(pszFile) + 1;

    while ( --uIndex != 0 )
    {
      const char ch = pszFile[uIndex - 1];

      if ( (ch == '\\') or (ch == '/') or (ch == ':') )
      {
        break;
      }
    }
    return pszFile + uIndex;
  }
}


void PrintUppercase(const char psz[])
{
  if ( psz != NULL )
  {
    size_t uIndex = 0;

    for(;;)
    {
      const char ch = psz[uIndex];

      if ( ch == '\0' )
      {
        break;
      }
      else
      {
        putchar( (char)toupper(ch) );
        ++uIndex;
      }
    }
  }
}


// <summary>
// Defines the entry point for the console application.
// </summary>
// <param name="argc">
// The number of arguments.
// </param>
// <param name="argv">
// The arguments: an array of zero terminated strings.
// </param>
// <returns>
// EXIT_SUCCESS if successful, EXIT_FAILURE otherwise.
// </returns>
int main(int argc, char* argv[])
{
  if ( argc <= 1 )
  {
    puts("");
    puts("Dekkerware DropFiles 1.10 - "
      "http://www.xs4all.nl/~nd/dekkerware/console/");
    puts("");
    puts("Drops files onto a window.");
    puts("");
    
    if ( (argv == NULL) or (argv[0] == NULL) )
    {
      puts("Arguments:");
    }
    else
    {
      PrintUppercase( ExtractFileName(argv[0]) );
      puts(" handle files");
      puts("");
    }
    puts("  handle  A hexadecimal number that specifies the target window.");
    puts("  files   A list of filenames to be dropped.");
    puts("");
    printf("The handle should have 1 to %d digits. ", 2 * sizeof(long) );
    puts("If an empty string is specified as handle, "
      "you will be prompted to enter one. "
      "If no filenames are specified, you will be prompted to enter them.");

    return EXIT_SUCCESS;
  }
  else // argc > 1
  {
    HWND hWindow = NULL;
    bool bHandleIsSpecified = false;
    bool bDropped;
    DWORD dwErrorCode;

    if ( (argv[1] != NULL) and (argv[1][0] != '\0') )
    {
      if ( HexToWindow(argv[1], &hWindow) )
      {
        bHandleIsSpecified = true;
      }
      else
      {
        printf("Invalid handle: ");
        printf(argv[1]);
        puts("");
      }
    }
    printf("Handle of target window: ");
    if ( bHandleIsSpecified )
    {
      // MSVC++ 2003 compiler has detected a 64-bit portability issue,
      // warning C4311: 'type cast' : pointer truncation from 'HWND'
      // to 'unsigned long'
      printf("%lx", (unsigned long)hWindow);
      puts("");
    }
    else
    {
      // The buffer should be large enough to store a hexidecimal
      // representation of a window handle.
      char szBuffer[2*sizeof(HWND) + 1];
      size_t uNumChars = 0;
      int iChar;

      fflush(stdout);
      iChar = getchar();

      while ( (iChar != EOF) and ( iChar != '\n') )
      {
        if( uNumChars < sizeof(szBuffer) ) 
        {
          szBuffer[uNumChars] = (char)iChar;
          iChar = getchar();
          ++uNumChars;
        }
        else
        {
          puts("The input string is too long.");
          return EXIT_FAILURE;
        }
      }
      if ( (uNumChars > 0) and (szBuffer[uNumChars - 1] == '\r') )
      {
        szBuffer[uNumChars - 1] = '\0';
      }
      else
      {
        if ( uNumChars < sizeof(szBuffer) )
        {
          szBuffer[uNumChars] = '\0';
        }
        else
        {
          puts("The input string is too long.");
          return EXIT_FAILURE;
        }
      }
      if ( szBuffer[0] == '\0' )
      {
        puts("No window handle was specified.");
        return EXIT_FAILURE;
      }
      else
      {
        if ( not HexToWindow(szBuffer, &hWindow) )
        {
          printf("Failed to convert \"%s\" to window handle.", szBuffer);
          puts("");
          return EXIT_FAILURE;
        }
      }
    }
    if ( argc > 2 )
    {
      const unsigned int uNumFiles = argc - 2;

      // GCC warning: initialization from incompatible pointer type
      const char * const * const ppszFiles = argv + 2;
      unsigned int uIndex;
      
      for( uIndex = 0; uIndex < uNumFiles; ++uIndex )
      {
        printf("File %u: ", uIndex + 1);
        printf(ppszFiles[uIndex]);
        puts("");
      }
      SetLastError(0);
      bDropped = DropFiles(hWindow, ppszFiles, uNumFiles);
      dwErrorCode = GetLastError();
    }
    else
    {
      unsigned int uNumFiles; 
      const char * const * const ppszFiles = AskForFiles(&uNumFiles);

      if ( (ppszFiles == NULL) && (uNumFiles > 0) )
      {
        puts("DropFiles error: failed to allocate memory.");
        return EXIT_FAILURE;
      }
      SetLastError(0);
      bDropped = DropFiles(hWindow, ppszFiles, uNumFiles);
      dwErrorCode = GetLastError();
      FreeArrayOfStrings(ppszFiles, uNumFiles);
    }

    if ( not bDropped )
    {
      puts("DropFiles has failed.");
    }

    if ( dwErrorCode == 0 )
    {
      if ( bDropped )
      {
        puts("DropFiles has dropped successfully!");
        return EXIT_SUCCESS;
      }
    }
    else
    {
      char * pszMessageBuffer = NULL;

      // The error code should be in <winerror.h>.
      printf("Error code %li", (long)dwErrorCode);

      if ( FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM | 
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dwErrorCode,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (char *) &pszMessageBuffer,
        0,
        NULL ) != 0 )
      {
        if ( pszMessageBuffer != NULL )
        {
          printf(": ");
          printf(pszMessageBuffer);
          LocalFree(pszMessageBuffer);
        }
      }
    }
    return EXIT_FAILURE;
  }
}

