2
votes

I currently have a method that uses ShellExecute to open files passed in by the user. Recently it was discovered that when a filename contains an apostrophe (ALT+0146) or Mac simple quote, ShellExecute returns a File Not Found Error. Example filename: "C:\Users\AMS\Documents\te'st.txt"

Code:

void sys_ShellExecute( PA_PluginParameters params )
{

LONG_PTR returnValue = 0;
char returnText[255]; // MWD & Mark De Wever #12225
INT_PTR  howToShow;
char *pChar;

char *operation = NULL;
char *file = NULL;
char *parameters = NULL;
char *directory = NULL;

//PA_Unistring *file; // AMS 2/24/14

// Get the function parameters.
operation = getTextParameter(params, 1);
file = getTextParameter(params, 2);
//file = PA_GetStringParameter(params, 2); // AMS 2/24/14
parameters = getTextParameter(params, 3);
directory = getTextParameter(params, 4);
howToShow = PA_GetLongParameter( params, 5 ); 



if ((strcmp(_strlwr(operation), "open")         != 0) &&
     (strcmp(_strlwr(operation),  "explore")    != 0) &&
     (strcmp(_strlwr(operation),  "print")      != 0) &&
     (file == NULL || strlen(file) == 0)) { 
    //strcpy(returnText, "Invalid Operation");
    strncpy(returnText, message->InvalidOperation, 255); // Mark De Wever #12225 replaced the line above
}
else if (howToShow > 11) {
    //strcpy(returnText, "Invalid HowToShow Constant");
    strncpy(returnText, message->InvalidShowConstant, 255); // Mark De Wever #12225 replaced the line above
}
else
{
    pChar = file; // added 10/28/02 shellExecute wants backslashes
    do {
        if (*pChar == '/') {
            *pChar = '\\';
        }

    } while (*pChar++ != '\0') ;

    pChar = directory;
    do  {
        if (*pChar == '/') { 
            *pChar = '\\';
        }
    } while (*pChar++ != '\0');

    returnValue = (LONG_PTR) ShellExecute(NULL, operation, file, parameters, directory, howToShow);

    strcpy(returnText, "");
    if (returnValue <= 32) { // error occurred
        switch (returnValue)
        {
        case ERROR_FILE_NOT_FOUND :
            //strcpy(returnText, "File Not Found");     
            strncpy(returnText, message->FileNotFound, 255); // Mark De Wever #12225 replaced line above
            break;

        case ERROR_PATH_NOT_FOUND :
            //strcpy(returnText, "Path Not Found");
            strncpy(returnText, message->PathNotFound, 255); // Mark De Wever #12225 Replaced line above
            break;

        case ERROR_BAD_FORMAT :
            //strcpy(returnText, ".EXE File is Invalid");       
            strncpy(returnText, message->BadFormat, 255); // Mark De Wever #12225 Replaced line above
            break;

        case SE_ERR_ACCESSDENIED :
            //strcpy(returnText, "OS Denied Access to File");       
            strncpy(returnText, message->AccessDenied, 255); // Mark De Wever #12225 Replaced line above
            break;

        case SE_ERR_ASSOCINCOMPLETE  :
            //strcpy(returnText, "File Name Association is Incomplete or Invalid");     
            strncpy(returnText, message->AssocIncomplete, 255); // Mark De Wever #12225 Replaced line above
            break;

        case SE_ERR_DDEBUSY  :
        case SE_ERR_DDEFAIL   :
            //strcpy(returnText, "DDE Transaction Could Not be Completed");
    strncpy(returnText, message->DDEFail, 255); // Mark De Wever #12225 Replaced line above
            break;

        case SE_ERR_DDETIMEOUT   :
            //strcpy(returnText, "DDE Request Timed Out");      
            strncpy(returnText, message->DDETimeOut, 255); // Mark De Wever #12225 Replaced line above
            break;

        case SE_ERR_DLLNOTFOUND    :
            //strcpy(returnText, "DLL Libray Not Found");
    strncpy(returnText, message->DLLNotFound, 255); // Mark De Wever #12225 Replaced line above
            break;

        case SE_ERR_NOASSOC   :
            //strcpy(returnText, "No Application Associated with File Extenstion");
    strncpy(returnText, message->NoAssoc, 255); // Mark De Wever #12225 Replaced line above
            break;

        case SE_ERR_OOM :
            //strcpy(returnText, "Insufficient Memory");
    strncpy(returnText, message->OOM, 255); // Mark De Wever #12225 Replaced line above
            break;

        case SE_ERR_SHARE    :
            //strcpy(returnText, "Sharing Violation Occurred");
    strncpy(returnText, message->ShareViolation, 255); // Mark De Wever #12225 Replaced line above
            break;

        default:
            //strcpy(returnText, "Unknown error occurred"); 
            strncpy(returnText, message->Default, 255); // Mark De Wever #12225 Replaced line above
            break;
        }
    }
}

freeTextParameter(operation);
freeTextParameter(file);
freeTextParameter(parameters);
freeTextParameter(directory);

PA_ReturnText( params, returnText, strlen(returnText));
}

getTextParameter code:

char * getTextParameter(PA_PluginParameters param, short index)
{

char *textValue = NULL;
LONG_PTR textLength = 0;

textLength = PA_GetTextParameter( param, index, 0L ) + 1;

// modified by Mark de Wever on 20060721
   // textLength was always > 0 due to the +1
  // revert change since sys_ShellExecute
  // fails if NULL pointer is returned...
  // So if malloc fails it will crash 4D
  // if(textLength > 1) {
if(textLength > 0) {
    textValue = (char *)malloc(textLength);

    if(textValue != NULL) {

        memset(textValue, 0, textLength);
        textLength = PA_GetTextParameter( param, index, textValue );

    }

}

return textValue;

}

PA_GetTextParameter code:

LONG_PTR PA_GetTextParameter( PA_PluginParameters params, short index, char* text ) 
{
PA_Unistring            *UnistringText; 
LONG_PTR                length;
char                    *textParameter;


UnistringText = PA_GetStringParameter( params, index);

if(text != 0L){

    textParameter = malloc((UnistringText->fLength + 1) * sizeof(char));
    memset(textParameter, 0, ((UnistringText->fLength + 1) * sizeof(char)));


    wcstombs(textParameter, UnistringText->fString, UnistringText->fLength);
    textParameter[strlen(textParameter)] = '\0';

    strcpy(text, textParameter);

    free (textParameter);

    length = strlen(text);


}else{
    length = UnistringText->fLength;
}

return length;
}

I noticed that wcstombs returns the filename without the apostrophe in textParameter and the full filename in UnistringText->fString.

I have tried using ShellExecuteW, ShellExecuteA, setting file to a unicode string, and many other variations and nothing seems to work. Am I missing something? Is there a way to work around this?

1
Try the processmoitor utility. With this program you can monitor which files are opened by which process. It's very powerful and may help you to spot the problem.Jabberwocky
If you call ShellExecuteW passing the filename as a literal unicode string (e.g. L"C:\\Users\\AMS\\Documents\\te'st.txt"), does it work? If so, don't apply any conversion to the original string (UnistringText->fString) and it should work fine.arx

1 Answers

2
votes

ALT+0146 is Unicode codepoint U+2019 SINGLE QUOTATION MARK. That is not the same as Unicode codepoint U+0027 APOSTROPHE. U+0027 is in the ASCII range so wcstomb() will preserve it as-is, but U+2019 is outside of ASCII and thus subject to charset/codepage conversions. If wcstombs() is omitting U+2019 than that usually means Windows' default codepage does not support that particular codepoint. However, you are also not taking into account that the length of a narrow string can be different than the length of the wide version of the same string, so that might also play a factor.

In any case, since your input parameters are Unicode strings to begin with, you really should be using Uncode API functions (like ShellExecuteW()) and not convert the Unicode strings to MBS at all.

void sys_ShellExecute( PA_PluginParameters params )
{
    LONG_PTR returnValue;
    char returnText[255]; // MWD & Mark De Wever #12225
    wchar_t *pChar;

    // Get the function parameters.
    wchar_t *operation = getTextParameter(params, 1);
    wchar_t *file = getTextParameter(params, 2);
    wchar_t *parameters = getTextParameter(params, 3);
    wchar_t *directory = getTextParameter(params, 4);
    INT_PTR howToShow = PA_GetLongParameter( params, 5 ); 

    if ((lstrcmpiW(operation, L"open") != 0) &&
       (lstrcmpiW(operation,  L"explore") != 0) &&
       (lstrcmpiW(operation,  L"print") != 0)) {
       strncpy(returnText, message->InvalidOperation, 255); // Mark De Wever #12225 replaced the line above
    }
    else if ((file == NULL) || (lstrlenW(file) == 0)) {
       strncpy(returnText, message->InvalidFile, 255); // Mark De Wever #12225 replaced the line above
    } 
    else if (howToShow > 11) {
        strncpy(returnText, message->InvalidShowConstant, 255); // Mark De Wever #12225 replaced the line above
    }
    else
    {
        pChar = file; // added 10/28/02 shellExecute wants backslashes
        do {
            if (*pChar == L'/') {
                *pChar = L'\\';
            }
        } while (*CharNextW(pChar) != L'\0') ;

        if (directory != NULL) {    
            pChar = directory;
            while (*pChar != L'\0') {
                if (*pChar == L'/') { 
                    *pChar = L'\\';
                }
                pChar = CharNext(pChar);
            }
        }

        returnValue = (LONG_PTR) ShellExecuteW(NULL, operation, file, parameters, directory, howToShow);

        strcpy(returnText, "");
        if (returnValue <= 32) { // error occurred
            switch (returnValue)
            {
                case ERROR_FILE_NOT_FOUND :
                    strncpy(returnText, message->FileNotFound, 255); // Mark De Wever #12225 replaced line above
                    break;

                case ERROR_PATH_NOT_FOUND :
                    strncpy(returnText, message->PathNotFound, 255); // Mark De Wever #12225 Replaced line above
                    break;

                case ERROR_BAD_FORMAT :
                    strncpy(returnText, message->BadFormat, 255); // Mark De Wever #12225 Replaced line above
                    break;

                case SE_ERR_ACCESSDENIED :
                    strncpy(returnText, message->AccessDenied, 255); // Mark De Wever #12225 Replaced line above
                    break;

                case SE_ERR_ASSOCINCOMPLETE  :
                    strncpy(returnText, message->AssocIncomplete, 255); // Mark De Wever #12225 Replaced line above
                    break;

                case SE_ERR_DDEBUSY  :
                case SE_ERR_DDEFAIL   :
                    strncpy(returnText, message->DDEFail, 255); // Mark De Wever #12225 Replaced line above
                    break;

                case SE_ERR_DDETIMEOUT   :
                    strncpy(returnText, message->DDETimeOut, 255); // Mark De Wever #12225 Replaced line above
                    break;

                case SE_ERR_DLLNOTFOUND    :
                    strncpy(returnText, message->DLLNotFound, 255); // Mark De Wever #12225 Replaced line above
                    break;

                case SE_ERR_NOASSOC   :
                    strncpy(returnText, message->NoAssoc, 255); // Mark De Wever #12225 Replaced line above
                    break;

                case SE_ERR_OOM :
                    strncpy(returnText, message->OOM, 255); // Mark De Wever #12225 Replaced line above
                    break;

                case SE_ERR_SHARE    :
                    strncpy(returnText, message->ShareViolation, 255); // Mark De Wever #12225 Replaced line above
                    break;

                default:
                    strncpy(returnText, message->Default, 255); // Mark De Wever #12225 Replaced line above
                    break;
            }
        }
    }

    freeTextParameter(operation);
    freeTextParameter(file);
    freeTextParameter(parameters);
    freeTextParameter(directory);

    PA_ReturnText( params, returnText, strlen(returnText));
}

wchar_t * getTextParameter(PA_PluginParameters param, short index)
{
    wchar_t *textValue = NULL;
    LONG_PTR textLength = PA_GetTextParameter( param, index, 0L ) + 1;

    // modified by Mark de Wever on 20060721
    // textLength was always > 0 due to the +1
    // revert change since sys_ShellExecute
    // fails if NULL pointer is returned...
    // So if malloc fails it will crash 4D
    // if(textLength > 1) {
    if (textLength > 0) {
        textValue = (wchar_t *) malloc(textLength * sizeof(wchar_t));
        if(textValue != NULL) {
            memset(textValue, 0, textLength * sizeof(wchar_t));
            if (textLength > 1) {
                PA_GetTextParameter( param, index, textValue );
            }
        }
    }

    return textValue;
}

void freeTextParameter(wchar_t *param)
{
    free(param);
}

LONG_PTR PA_GetTextParameter( PA_PluginParameters params, short index, wchar_t* text ) 
{
    PA_Unistring            *UnistringText; 
    LONG_PTR                length;

    UnistringText = PA_GetStringParameter( params, index);
    if (UnistringText != NULL) {
        length = UnistringText->fLength;
    } else {
        length = 0;
    }

    if (text != NULL) {
        if (UnistringText != NULL) {
            memcpy(text, UnistringText->fString, length * sizeof(wchar_t));
        }
        text[length] = L'\0';
    }

    return length;
}