/* shortcut.c: Read shortcuts. This part of the code must be in C because
               the C++ interface to COM doesn't work without -fvtable-thunk
	       which is too dangerous to use.

   Copyright 2001 Red Hat, Inc.

This file is part of Cygwin.

This software is a copyrighted work licensed under the terms of the
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
details. */

#define WIN32_LEAN_AND_MEAN
#include <shlobj.h>
#include "winsup.h"
#include <ctype.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <errno.h>
#include "shortcut.h"

/* TODO:
   Currently duplicated from path.h. Later rearrangement of path.h
   to allow including from plain C would be better. */
/* This is needed to avoid including path.h which is a pure C++ header. */
#define PATH_SYMLINK	 MOUNT_SYMLINK
#define PATH_EXEC	 MOUNT_EXEC
#define PATH_CYGWIN_EXEC MOUNT_CYGWIN_EXEC
#define PATH_ALL_EXEC	 (PATH_CYGWIN_EXEC | PATH_EXEC)

/* TODO: Ditto. */
static BOOL
has_exec_chars (const char *buf, int len)
{
  return len >= 2 &&
	 ((buf[0] == '#' && buf[1] == '!') ||
	  (buf[0] == ':' && buf[1] == '\n') ||
	  (buf[0] == 'M' && buf[1] == 'Z'));
}

char shortcut_header[SHORTCUT_HDR_SIZE];
BOOL shortcut_initalized = FALSE;

void
create_shortcut_header (void)
{
  if (!shortcut_initalized)
    {
      shortcut_header[0] = 'L';
      shortcut_header[4] = '\001';
      shortcut_header[5] = '\024';
      shortcut_header[6] = '\002';
      shortcut_header[12] = '\300';
      shortcut_header[19] = 'F';
      shortcut_header[20] = '\f';
      shortcut_header[60] = '\001';
      shortcut_initalized = TRUE;
    }
}

static BOOL
cmp_shortcut_header (const char *file_header)
{
  create_shortcut_header ();
  return memcmp (shortcut_header, file_header, SHORTCUT_HDR_SIZE);
}

int
check_shortcut (const char *path, DWORD fileattr, HANDLE h,
		char *contents, int *error, unsigned *pflags)
{
  HRESULT hres;
  IShellLink *psl = NULL;
  IPersistFile *ppf = NULL;
  WCHAR wc_path[MAX_PATH];
  char file_header[SHORTCUT_HDR_SIZE];
  DWORD len = 0;
  int res = 0;
  DWORD got = 0;

  /* Initialize COM library. */
  CoInitialize (NULL);

  /* Get a pointer to the IShellLink interface. */
  hres = CoCreateInstance (&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
			   &IID_IShellLink, (void **)&psl);
  if (FAILED (hres))
    goto close_it;
  /* Get a pointer to the IPersistFile interface. */
  hres = psl->lpVtbl->QueryInterface (psl, &IID_IPersistFile, (void **)&ppf);
  if (FAILED (hres))
    goto close_it;
  /* Load the shortcut. */
  MultiByteToWideChar(CP_ACP, 0, path, -1, wc_path, MAX_PATH);
  hres = ppf->lpVtbl->Load (ppf, wc_path, STGM_READ);
  if (FAILED (hres))
    goto close_it;
  /* Read the files header information. This is used to check for a
     Cygwin or U/WIN shortcut or later to check for executable files. */
  if (!ReadFile (h, file_header, SHORTCUT_HDR_SIZE, &got, 0))
    {
      *error = EIO;
      goto close_it;
    }
  /* Try the description (containing a POSIX path) first. */
  if (fileattr & FILE_ATTRIBUTE_READONLY)
    {
      /* Check header if the shortcut is really created by Cygwin or U/WIN. */
      if (got == SHORTCUT_HDR_SIZE && !cmp_shortcut_header (file_header))
        {
	  hres = psl->lpVtbl->GetDescription (psl, contents, MAX_PATH);
	  if (FAILED (hres))
	    goto file_not_symlink;
	  len = strlen (contents);
	}
    }
#if TREAT_NATIVE_SHORTCUTS_AS_SYMLINKS
  /* No description or not R/O: Check the "official" path. */
  if (len == 0)
    {
      char full_path[MAX_PATH];
      WIN32_FIND_DATA wfd;

      /* Convert to full path (easy way) */
      if ((path[0] == '\\' && path[1] == '\\')
	  || (_toupper (path[0]) >= 'A' && _toupper (path[0]) <= 'Z'
	      && path[1] == ':'))
	len = 0;
      else
	{
	  len = GetCurrentDirectory (MAX_PATH, full_path);
	  if (path[0] == '\\')
	    len = 2;
	  else if (full_path[len - 1] != '\\')
	    strcpy (full_path + len++, "\\");
	}
      strcpy (full_path + len, path);
      /* Set relative path inside of IShellLink interface. */
      hres = psl->lpVtbl->SetRelativePath (psl, full_path, 0);
      if (FAILED (hres))
	goto file_not_symlink;
      /* Get the path to the shortcut target. */
      hres = psl->lpVtbl->GetPath (psl, contents, MAX_PATH, &wfd, 0);
      if (FAILED(hres))
	goto file_not_symlink;
    }
#endif
  res = strlen (contents);
  if (res) /* It's a symlink.  */
    *pflags = PATH_SYMLINK;
  goto close_it;

file_not_symlink:
  /* Not a symlink, see if executable.  */
  if (!(*pflags & PATH_ALL_EXEC) && has_exec_chars (file_header, got))
    *pflags |= PATH_EXEC;

close_it:
  /* Release the pointer to IPersistFile. */
  if (ppf)
    ppf->lpVtbl->Release(ppf);
  /* Release the pointer to IShellLink. */
  if (psl)
    psl->lpVtbl->Release(psl);
  /* Uninitialize COM library. */
  CoUninitialize ();
  CloseHandle (h);

  return res;
}
