/*
 * Safe wrappers to launch arbitrary URLs with ShellExecute(Ex) -- neatly
 * sidestep normalization issues, shifting the burden of validation on the
 * external application registered to handle the URL
 *
 * Author: KJK::Hyperion <hackbunny@s0ftpj.org>
 */

/*
 * Copyright (c) 2007 KJK::Hyperion
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include <shellapi.h>
#include <shlwapi.h>
#include <wininet.h>

#include <strsafe.h>

BOOL ShellExecuteURLExInternal(LPSHELLEXECUTEINFO lpExecInfo)
{
	BOOL bRet;
	DWORD dwErr;
	HRESULT hr;
	PARSEDURL pu;
	TCHAR szSchemeBuffer[INTERNET_MAX_SCHEME_LENGTH + 1];
	HKEY hkeyClass;

	/* Default error codes */
	bRet = FALSE;
	dwErr = ERROR_INVALID_PARAMETER;
	hr = S_OK;

	lpExecInfo->hInstApp =
		(HINSTANCE)UlongToHandle(SE_ERR_ACCESSDENIED);

	/* Validate parameters */
	if
	(
		lpExecInfo->cbSize == sizeof(*lpExecInfo) &&
		lpExecInfo->lpFile != NULL &&
		(lpExecInfo->fMask & SEE_MASK_INVOKEIDLIST) == 0 &&
		(lpExecInfo->fMask & SEE_MASK_CLASSNAME) == 0 &&
		(lpExecInfo->fMask & 0x00400000) == 0 /* SEE_MASK_FILEANDURL */
	)
	{
		/* Extract the scheme out of the URL */
		pu.cbSize = sizeof(pu);
		hr = ParseURL(lpExecInfo->lpFile, &pu);

		/* Is the URL really, unambiguously an URL? */
		if
		(
			SUCCEEDED(hr) &&
			pu.pszProtocol == lpExecInfo->lpFile &&
			pu.pszProtocol[pu.cchProtocol] == TEXT(':')
		)
		{
			/* We need the scheme name NUL-terminated, so we copy it */
			hr = StringCbCopyN
			(
				szSchemeBuffer,
				sizeof(szSchemeBuffer),
				pu.pszProtocol,
				pu.cchProtocol * sizeof(TCHAR)
			);

			if(SUCCEEDED(hr))
			{
				/* Is the URL scheme a registered ProgId? */
				hr = AssocQueryKey
				(
					ASSOCF_INIT_IGNOREUNKNOWN,
					ASSOCKEY_CLASS,
					szSchemeBuffer,
					NULL,
					&hkeyClass
				);

				if(SUCCEEDED(hr))
				{
					/* Is the ProgId really an URL scheme? */
					dwErr = RegQueryValueEx
					(
						hkeyClass,
						TEXT("URL Protocol"),
						NULL,
						NULL,
						NULL,
						NULL
					);

					/* All clear! */
					if(dwErr == NO_ERROR || dwErr == ERROR_MORE_DATA)
					{
						/* Don't let ShellExecuteEx guess */
						lpExecInfo->fMask |= SEE_MASK_CLASSKEY;
						lpExecInfo->lpClass = NULL;
						lpExecInfo->hkeyClass = hkeyClass;

						/* Finally, execute the damn URL */
						bRet = ShellExecuteEx(lpExecInfo);

						/* To preserve ShellExecuteEx's last error */
						dwErr = NO_ERROR;
					}

					RegCloseKey(hkeyClass);
				}
			}
		}
	}

	/* Last error was a HRESULT */
	if(FAILED(hr))
	{
		/* Try to dissect it */
		if(HRESULT_FACILITY(hr) == FACILITY_WIN32)
			dwErr = HRESULT_CODE(hr);
		else
			dwErr = hr;
	}

	/* We have a last error to set */
	if(dwErr)
		SetLastError(dwErr);

	return bRet;
}

BOOL ShellExecuteURLEx(LPSHELLEXECUTEINFO lpExecInfo)
{
	BOOL bRet;
	SHELLEXECUTEINFO ExecInfo;

	/* We use a copy of the parameters, because you never know */
	CopyMemory(&ExecInfo, lpExecInfo, sizeof(ExecInfo));

	/* Do the magic */
	bRet = ShellExecuteURLExInternal(&ExecInfo);

	/* These need to be copied back */
	lpExecInfo->hInstApp = ExecInfo.hInstApp;
	lpExecInfo->hProcess = ExecInfo.hProcess;
	return bRet;
}

HINSTANCE ShellExecuteURL
(
	HWND hwnd,
	LPCTSTR lpOperation,
	LPCTSTR lpFile,
	LPCTSTR lpParameters,
	LPCTSTR lpDirectory,
	INT nShowCmd
)
{
	SHELLEXECUTEINFO ExecuteInfo;

	ExecuteInfo.fMask = SEE_MASK_FLAG_NO_UI; /* Odd but true */
	ExecuteInfo.hwnd = hwnd;
	ExecuteInfo.cbSize = sizeof(ExecuteInfo);
	ExecuteInfo.lpVerb = lpOperation;
	ExecuteInfo.lpFile = lpFile;
	ExecuteInfo.lpParameters = lpParameters;
	ExecuteInfo.lpDirectory = lpDirectory;
	ExecuteInfo.nShow = nShowCmd;

	ShellExecuteURLExInternal(&ExecuteInfo);

	return ExecuteInfo.hInstApp;
}

/* EOF */
