/**************************************************************
 * 
 * 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 "rtl/allocator.hxx"
#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, rtl::Allocator<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, rtl::Allocator<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, rtl::Allocator<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, rtl::Allocator<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 occured 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;
}