/**************************************************************
 *
 * 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.
 *
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sal.hxx"

#define UNICODE
#define _UNICODE

#ifndef WIN32_LEAN_AND_MEAN
#   define WIN32_LEAN_AND_MEAN
# ifdef _MSC_VER
#   pragma warning(push,1) /* disable warnings within system headers */
# endif
#   include <windows.h>
# ifdef _MSC_VER
#   pragma warning(pop)
# endif
#   include <tchar.h>
#   undef WIN32_LEAN_AND_MEAN
#endif
#include "procimpl.h"
#include <rtl/ustring.hxx>
#include <rtl/ustrbuf.hxx>
#include "secimpl.h"
#include <osl/file.hxx>

#include <list>
#include <vector>
#include <algorithm>
#include <string>

//#################################################
extern "C" oslFileHandle SAL_CALL osl_createFileHandleFromOSHandle( HANDLE hFile, sal_uInt32 uFlags );

//#################################################
const sal_Unicode NAME_VALUE_SEPARATOR = TEXT('=');
const sal_Char* SPACE = " ";
const rtl::OUString ENV_COMSPEC = rtl::OUString::createFromAscii("COMSPEC");
const rtl::OUString QUOTE = rtl::OUString::createFromAscii("\"");

namespace /* private */
{
    //#################################################
    typedef std::list<rtl::OUString> string_container_t;
    typedef string_container_t::iterator string_container_iterator_t;
    typedef string_container_t::const_iterator string_container_const_iterator_t;
    typedef std::pair<string_container_iterator_t, string_container_iterator_t> iterator_pair_t;
    typedef std::vector<sal_Unicode > environment_container_t;

    //#################################################
    /* Function object that compares two strings that are
       expected to be environment variables in the form
       "name=value". Only the 'name' part will be compared.
       The comparison is in upper case and returns true
       if the first of both strings is less than the
       second one. */
    struct less_environment_variable :
        public std::binary_function<rtl::OUString, rtl::OUString, bool>
    {
        bool operator() (const rtl::OUString& lhs, const rtl::OUString& rhs) const
        {
            OSL_ENSURE((lhs.indexOf(NAME_VALUE_SEPARATOR) > -1) && \
                        (rhs.indexOf(NAME_VALUE_SEPARATOR) > -1), \
                        "Malformed environment variable");

            // Windows compares environment variables uppercase
            // so we do it, too
            return (rtl_ustr_compare_WithLength(
                lhs.toAsciiUpperCase().pData->buffer,
                lhs.indexOf(NAME_VALUE_SEPARATOR),
                rhs.toAsciiUpperCase().pData->buffer,
                rhs.indexOf(NAME_VALUE_SEPARATOR)) < 0);
        }
    };

    //#################################################
    /* Function object used by for_each algorithm to
       calculate the sum of the length of all strings
       in a string container. */
    class sum_of_string_lengths
    {
    public:
        //--------------------------------
        sum_of_string_lengths() : sum_(0) {}

        //--------------------------------
        void operator() (const rtl::OUString& string)
        {
            OSL_ASSERT(string.getLength());

            // always include the terminating '\0'
            if (string.getLength())
                sum_ += string.getLength() + 1;
        }

        //--------------------------------
        operator size_t () const
        {
            return sum_;
        }
    private:
        size_t sum_;
    };

    //#################################################
    inline size_t calc_sum_of_string_lengths(const string_container_t& string_cont)
    {
        return std::for_each(
            string_cont.begin(), string_cont.end(), sum_of_string_lengths());
    }

    //#################################################
    void read_environment(/*out*/ string_container_t* environment)
    {
        // GetEnvironmentStrings returns a sorted list, Windows
        // sorts environment variables upper case
        LPTSTR env = reinterpret_cast<LPTSTR>(GetEnvironmentStrings());
        LPTSTR p   = env;

        while (size_t l = _tcslen(p))
        {
            environment->push_back(reinterpret_cast<const sal_Unicode*>(p));
            p += l + 1;
        }
        FreeEnvironmentStrings(env);
    }

    //#################################################
    /* the environment list must be sorted, new values
    should either replace existing ones or should be
    added to the list, environment variables will
    be handled case-insensitive */
    bool create_merged_environment(
        rtl_uString* env_vars[],
        sal_uInt32 env_vars_count,
        /*in|out*/ string_container_t* merged_env)
    {
        OSL_ASSERT(env_vars && env_vars_count > 0 && merged_env);

        read_environment(merged_env);

        for (sal_uInt32 i = 0; i < env_vars_count; i++)
        {
            rtl::OUString env_var = rtl::OUString(env_vars[i]);

            if (env_var.getLength() == 0)
                return false;

            iterator_pair_t iter_pair = std::equal_range(
                merged_env->begin(),
                merged_env->end(),
                env_var,
                less_environment_variable());

            if (env_var.indexOf(NAME_VALUE_SEPARATOR) == -1)
            {
                merged_env->erase(iter_pair.first, iter_pair.second);
            }
            else
            {
                if (iter_pair.first != iter_pair.second) // found
                    *iter_pair.first = env_var;
                else // not found
                    merged_env->insert(iter_pair.first, env_var);
            }
        }
        return true;
    }

    //#################################################
    /* Create a merged environment */
    bool setup_process_environment(
        rtl_uString* environment_vars[],
        sal_uInt32 n_environment_vars,
        /*in|out*/ environment_container_t& environment)
    {
        string_container_t merged_env;
	    if (!create_merged_environment(environment_vars, n_environment_vars, &merged_env))
	        return false;

        // allocate enough space for the '\0'-separated environment strings and
        // a final '\0'
        environment.resize(calc_sum_of_string_lengths(merged_env) + 1);

        string_container_const_iterator_t iter = merged_env.begin();
        string_container_const_iterator_t iter_end = merged_env.end();

        sal_uInt32 pos = 0;
        for (/**/; iter != iter_end; ++iter)
        {
            rtl::OUString envv = *iter;

            OSL_ASSERT(envv.getLength());

            sal_uInt32 n = envv.getLength() + 1; // copy the final '\0', too
            rtl_copyMemory(
                reinterpret_cast<void*>(&environment[pos]),
                reinterpret_cast<const void*>(envv.getStr()),
                n * sizeof(sal_Unicode));
            pos += n;
        }
        environment[pos] = 0; // append a final '\0'

        return true;
    }

    //##########################################################
    /*  In contrast to the Win32 API function CreatePipe with
        this function the caller is able to determine separately
        which handle of the pipe is inheritable. */
    bool create_pipe(
        PHANDLE p_read_pipe,
        bool    b_read_pipe_inheritable,
        PHANDLE p_write_pipe,
        bool    b_write_pipe_inheritable,
        LPVOID  p_security_descriptor = NULL,
        DWORD   pipe_size = 0)
    {
        SECURITY_ATTRIBUTES	sa;
	    sa.nLength              = sizeof(SECURITY_ATTRIBUTES);
	    sa.lpSecurityDescriptor = p_security_descriptor;
	    sa.bInheritHandle       = b_read_pipe_inheritable || b_write_pipe_inheritable;

	    BOOL   bRet  = FALSE;
	    HANDLE hTemp = NULL;

	    if (!b_read_pipe_inheritable && b_write_pipe_inheritable)
	    {
	        bRet = CreatePipe(&hTemp, p_write_pipe, &sa, pipe_size);

	        if (bRet && !DuplicateHandle(GetCurrentProcess(), hTemp,
	                        GetCurrentProcess(), p_read_pipe, 0, FALSE,
	                        DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS))
	        {
	            CloseHandle(hTemp);
	            CloseHandle(*p_read_pipe);
	            return false;
	        }
	    }
	    else if (b_read_pipe_inheritable && !b_write_pipe_inheritable)
	    {
	        bRet = CreatePipe(p_read_pipe, &hTemp, &sa, pipe_size);

	        if (bRet && !DuplicateHandle(GetCurrentProcess(), hTemp,
	                        GetCurrentProcess(), p_write_pipe, 0, FALSE,
	                        DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS))
	        {
	            CloseHandle(hTemp);
	            CloseHandle(*p_write_pipe);
	            return false;
	        }
	    }
	    else
	    {
	        bRet = CreatePipe(p_read_pipe, p_write_pipe, &sa, pipe_size);
	    }
	    return bRet;
    }

    //#########################################################
    // Add a quote sign to the start and the end of a string
    // if not already present
    rtl::OUString quote_string(const rtl::OUString& string)
    {
        rtl::OUStringBuffer quoted;
        if (string.indexOf(QUOTE) != 0)
            quoted.append(QUOTE);

        quoted.append(string);

        if (string.lastIndexOf(QUOTE) != (string.getLength() - 1))
            quoted.append(QUOTE);

        return quoted.makeStringAndClear();
    }

    //The parameter path must be a system path. If it is longer than 260 characters
    //then it is shortened using the GetShortPathName function. This function only
    //works if the path exists. Because "path" can be the path to an executable, it
    //may not have the file extension ".exe". However, if the file on disk has the
    //".exe" extension, then the function will fail. In this case a second attempt
    //is started by adding the parameter "extension" to "path".
    rtl::OUString getShortPath(rtl::OUString const & path, rtl::OUString const & extension)
    {
        rtl::OUString ret(path);
        if (path.getLength() > 260)
        {
            std::vector<sal_Unicode> vec(path.getLength() + 1);
            //GetShortPathNameW only works if the file can be found!
            const DWORD len = GetShortPathNameW(
                reinterpret_cast<LPCWSTR>(path.getStr()), reinterpret_cast<LPWSTR>(&vec[0]), path.getLength() + 1);

            if (!len && GetLastError() == ERROR_FILE_NOT_FOUND
                && extension.getLength())
            {
                const rtl::OUString extPath(path + extension);
                std::vector<sal_Unicode > vec2( extPath.getLength() + 1);
                const DWORD len2 = GetShortPathNameW(
                    reinterpret_cast<LPCWSTR>(extPath.getStr()), reinterpret_cast<LPWSTR>(&vec2[0]), extPath.getLength() + 1);
                ret = rtl::OUString(&vec2[0], len2);
            }
            else
            {
                ret = rtl::OUString(&vec[0], len);
            }
        }
        return ret;
    }
    //##########################################################
    // Returns the system path of the executable which can either
    // be provided via the strImageName parameter or as first
    // element of the strArguments list.
    // The returned path will be quoted if it contains spaces.
    rtl::OUString get_executable_path(
        rtl_uString* image_name,
        rtl_uString* cmdline_args[],
        sal_uInt32 n_cmdline_args,
        bool search_path)
    {
        rtl::OUString exe_name;

        if (image_name)
            exe_name = image_name;
        else if (n_cmdline_args)
            exe_name = rtl::OUString(cmdline_args[0]);

        rtl::OUString exe_url = exe_name;
        if (search_path)
            osl_searchFileURL(exe_name.pData, NULL, &exe_url.pData);

        rtl::OUString exe_path;
        if (osl_File_E_None != osl::FileBase::getSystemPathFromFileURL(exe_url, exe_path))
            return rtl::OUString();

        exe_path = getShortPath(exe_path, rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(".exe")));

        if (exe_path.indexOf(' ') != -1)
            exe_path = quote_string(exe_path);

        return exe_path;
    }

    //##########################################################
    rtl::OUString get_file_extension(const rtl::OUString& file_name)
    {
        sal_Int32 index = file_name.lastIndexOf('.');
        if ((index != -1) && ((index + 1) < file_name.getLength()))
            return file_name.copy(index + 1);

        return rtl::OUString();
    }

    //##########################################################
    bool is_batch_file(const rtl::OUString& file_name)
    {
        rtl::OUString ext = get_file_extension(file_name);
        return (ext.equalsIgnoreAsciiCaseAscii("bat") ||
                ext.equalsIgnoreAsciiCaseAscii("cmd") ||
                ext.equalsIgnoreAsciiCaseAscii("btm"));
    }

    //##########################################################
    rtl::OUString get_batch_processor()
    {
        rtl::OUString comspec;
        osl_getEnvironment(ENV_COMSPEC.pData, &comspec.pData);

        OSL_ASSERT(comspec.getLength());

        /* check if comspec path contains blanks and quote it if any */
        if (comspec.indexOf(' ') != -1)
            comspec = quote_string(comspec);

        return comspec;
    }

} // namespace private


//#################################################
oslProcessError SAL_CALL osl_executeProcess(
	rtl_uString *strImageName,
	rtl_uString *strArguments[],
	sal_uInt32   nArguments,
	oslProcessOption Options,
	oslSecurity Security,
	rtl_uString *strDirectory,
	rtl_uString *strEnvironmentVars[],
	sal_uInt32   nEnvironmentVars,
	oslProcess *pProcess
)
{
	return osl_executeProcess_WithRedirectedIO(
		strImageName,
		strArguments,
		nArguments,
		Options,
		Security,
		strDirectory,
		strEnvironmentVars,
		nEnvironmentVars,
		pProcess,
		NULL, NULL, NULL );
}

//#################################################
oslProcessError SAL_CALL osl_executeProcess_WithRedirectedIO(
	rtl_uString *ustrImageName,
	rtl_uString *ustrArguments[],
	sal_uInt32   nArguments,
	oslProcessOption Options,
	oslSecurity Security,
	rtl_uString *ustrDirectory,
	rtl_uString *ustrEnvironmentVars[],
	sal_uInt32 nEnvironmentVars,
	oslProcess *pProcess,
	oslFileHandle *pProcessInputWrite,
	oslFileHandle *pProcessOutputRead,
	oslFileHandle *pProcessErrorRead)
{
    rtl::OUString exe_path = get_executable_path(
        ustrImageName, ustrArguments, nArguments, (Options & osl_Process_SEARCHPATH));

    if (0 == exe_path.getLength())
        return osl_Process_E_NotFound;

    if (pProcess == NULL)
        return osl_Process_E_InvalidError;

    DWORD flags = NORMAL_PRIORITY_CLASS;
    rtl::OUStringBuffer command_line;

    if (is_batch_file(exe_path))
    {
        rtl::OUString batch_processor = get_batch_processor();

        if (batch_processor.getLength())
        {
            /* cmd.exe does not work without a console window */
            if (!(Options & osl_Process_WAIT) || (Options & osl_Process_DETACHED))
                flags |= CREATE_NEW_CONSOLE;

            command_line.append(batch_processor);
            command_line.appendAscii(" /c ");
        }
        else
            // should we return here in case of error?
            return osl_Process_E_Unknown;
    }

    command_line.append(exe_path);

    /* Add remaining arguments to command line. If ustrImageName is NULL
       the first parameter is the name of the executable so we have to
       start at 1 instead of 0 */
    for (sal_uInt32 n = (NULL != ustrImageName) ? 0 : 1; n < nArguments; n++)
    {
        command_line.appendAscii(SPACE);

        /* Quote arguments containing blanks */
        if (rtl::OUString(ustrArguments[n]).indexOf(' ') != -1)
            command_line.append(quote_string(ustrArguments[n]));
        else
            command_line.append(ustrArguments[n]);
    }

    environment_container_t environment;
    LPVOID p_environment = NULL;

    if (nEnvironmentVars && ustrEnvironmentVars)
    {
        if (!setup_process_environment(
                ustrEnvironmentVars, nEnvironmentVars, environment))
            return osl_Process_E_InvalidError;

        flags |= CREATE_UNICODE_ENVIRONMENT;
        p_environment = &environment[0];
    }

    rtl::OUString cwd;
    if (ustrDirectory && ustrDirectory->length && (osl_File_E_None != osl::FileBase::getSystemPathFromFileURL(ustrDirectory, cwd)))
   	    return osl_Process_E_InvalidError;

    LPCWSTR	p_cwd = (cwd.getLength()) ? reinterpret_cast<LPCWSTR>(cwd.getStr()) : NULL;

	if ((Options & osl_Process_DETACHED) && !(flags & CREATE_NEW_CONSOLE))
		flags |= DETACHED_PROCESS;

    STARTUPINFO startup_info;
	memset(&startup_info, 0, sizeof(STARTUPINFO));

	startup_info.cb        = sizeof(STARTUPINFO);
	startup_info.dwFlags   = STARTF_USESHOWWINDOW;
	startup_info.lpDesktop = L"";

	/* Create pipes for redirected IO */
    HANDLE hInputRead  = NULL;
    HANDLE hInputWrite = NULL;
	if (pProcessInputWrite && create_pipe(&hInputRead, true, &hInputWrite, false))
	    startup_info.hStdInput = hInputRead;

	HANDLE hOutputRead  = NULL;
    HANDLE hOutputWrite = NULL;
	if (pProcessOutputRead && create_pipe(&hOutputRead, false, &hOutputWrite, true))
	    startup_info.hStdOutput = hOutputWrite;

	HANDLE hErrorRead  = NULL;
    HANDLE hErrorWrite = NULL;
	if (pProcessErrorRead && create_pipe(&hErrorRead, false, &hErrorWrite, true))
	    startup_info.hStdError = hErrorWrite;

	bool b_inherit_handles = false;
	if (pProcessInputWrite || pProcessOutputRead || pProcessErrorRead)
	{
	    startup_info.dwFlags |= STARTF_USESTDHANDLES;
		b_inherit_handles      = true;
	}

	switch(Options & (osl_Process_NORMAL | osl_Process_HIDDEN | osl_Process_MINIMIZED | osl_Process_MAXIMIZED | osl_Process_FULLSCREEN))
	{
		case osl_Process_HIDDEN:
			startup_info.wShowWindow = SW_HIDE;
            flags |= CREATE_NO_WINDOW; // ignored for non-console
                                       // applications; ignored on
                                       // Win9x
			break;

		case osl_Process_MINIMIZED:
			startup_info.wShowWindow = SW_MINIMIZE;
			break;

		case osl_Process_MAXIMIZED:
		case osl_Process_FULLSCREEN:
			startup_info.wShowWindow = SW_MAXIMIZE;
			break;

		default:
			startup_info.wShowWindow = SW_NORMAL;
	}

    rtl::OUString cmdline = command_line.makeStringAndClear();
    PROCESS_INFORMATION process_info;
    BOOL bRet = FALSE;

	if ((Security != NULL) && (((oslSecurityImpl*)Security)->m_hToken != NULL))
	{
		bRet = CreateProcessAsUser(
            ((oslSecurityImpl*)Security)->m_hToken,
			NULL, const_cast<LPTSTR>(reinterpret_cast<LPCTSTR>(cmdline.getStr())), NULL,  NULL,
		    b_inherit_handles, flags, p_environment, p_cwd,
			&startup_info, &process_info);
	}
	else
	{
		bRet = CreateProcess(
            NULL, const_cast<LPTSTR>(reinterpret_cast<LPCTSTR>(cmdline.getStr())), NULL,  NULL,
			b_inherit_handles, flags, p_environment, p_cwd,
			&startup_info, &process_info);
	}

	/* Now we can close the pipe ends that are used by the child process */

	if (hInputRead)
		CloseHandle(hInputRead);

	if (hOutputWrite)
		CloseHandle(hOutputWrite);

	if (hErrorWrite)
		CloseHandle(hErrorWrite);

	if (bRet)
	{
		CloseHandle(process_info.hThread);

		oslProcessImpl* pProcImpl = reinterpret_cast<oslProcessImpl*>(
		    rtl_allocateMemory(sizeof(oslProcessImpl)));

		if (pProcImpl != NULL)
		{
		    pProcImpl->m_hProcess  = process_info.hProcess;
		    pProcImpl->m_IdProcess = process_info.dwProcessId;

		    *pProcess = (oslProcess)pProcImpl;

		    if (Options & osl_Process_WAIT)
			    WaitForSingleObject(pProcImpl->m_hProcess, INFINITE);

		    if (pProcessInputWrite)
			    *pProcessInputWrite = osl_createFileHandleFromOSHandle(hInputWrite, osl_File_OpenFlag_Write);

		    if (pProcessOutputRead)
			    *pProcessOutputRead = osl_createFileHandleFromOSHandle(hOutputRead, osl_File_OpenFlag_Read);

		    if (pProcessErrorRead)
			    *pProcessErrorRead = osl_createFileHandleFromOSHandle(hErrorRead, osl_File_OpenFlag_Read);

		    return osl_Process_E_None;
	    }
    }

	/* if an error occurred we have to close the server side pipe ends too */

	if (hInputWrite)
		CloseHandle(hInputWrite);

	if (hOutputRead)
		CloseHandle(hOutputRead);

	if (hErrorRead)
		CloseHandle(hErrorRead);

	return osl_Process_E_Unknown;
}
