shlexec.c 100 KB
Newer Older
1 2 3
/*
 * Unit test of the ShellExecute function.
 *
4
 * Copyright 2005, 2016 Francois Gouget for CodeWeavers
5 6 7 8 9 10 11 12 13 14 15 16 17
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 20 21 22 23 24 25 26 27 28 29 30 31 32
 */

/* TODO:
 * - test the default verb selection
 * - test selection of an alternate class
 * - try running executables in more ways
 * - try passing arguments to executables
 * - ShellExecute("foo.shlexec") with no path should work if foo.shlexec is
 *   in the PATH
 * - test associations that use %l, %L or "%1" instead of %1
 * - ShellExecuteEx() also calls SetLastError() with meaningful values which
 *   we could check
 */

33 34 35
/* Needed to get SEE_MASK_NOZONECHECKS with the PSDK */
#define NTDDI_WINXPSP1 0x05010100
#define NTDDI_VERSION NTDDI_WINXPSP1
36
#define _WIN32_WINNT 0x0501
37

38 39 40
#include <stdio.h>
#include <assert.h>

41 42 43 44 45
#include "wtypes.h"
#include "winbase.h"
#include "windef.h"
#include "shellapi.h"
#include "shlwapi.h"
46
#include "ddeml.h"
47 48 49 50 51 52 53 54 55
#include "wine/test.h"

#include "shell32_test.h"


static char argv0[MAX_PATH];
static int myARGC;
static char** myARGV;
static char tmpdir[MAX_PATH];
56 57
static char child_file[MAX_PATH];
static DLLVERSIONINFO dllver;
58
static BOOL skip_shlexec_tests = FALSE;
59
static BOOL skip_noassoc_tests = FALSE;
60
static HANDLE dde_ready_event;
61 62


63 64
/***
 *
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
 * Helpers to read from / write to the child process results file.
 * (borrowed from dlls/kernel32/tests/process.c)
 *
 ***/

static const char* encodeA(const char* str)
{
    static char encoded[2*1024+1];
    char*       ptr;
    size_t      len,i;

    if (!str) return "";
    len = strlen(str) + 1;
    if (len >= sizeof(encoded)/2)
    {
        fprintf(stderr, "string is too long!\n");
        assert(0);
    }
    ptr = encoded;
    for (i = 0; i < len; i++)
        sprintf(&ptr[i * 2], "%02x", (unsigned char)str[i]);
    ptr[2 * len] = '\0';
    return ptr;
}

static unsigned decode_char(char c)
{
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    assert(c >= 'A' && c <= 'F');
    return c - 'A' + 10;
}

static char* decodeA(const char* str)
{
    static char decoded[1024];
    char*       ptr;
    size_t      len,i;

    len = strlen(str) / 2;
    if (!len--) return NULL;
    if (len >= sizeof(decoded))
    {
        fprintf(stderr, "string is too long!\n");
        assert(0);
    }
    ptr = decoded;
    for (i = 0; i < len; i++)
        ptr[i] = (decode_char(str[2 * i]) << 4) | decode_char(str[2 * i + 1]);
    ptr[len] = '\0';
    return ptr;
}

118
static void WINETEST_PRINTF_ATTR(2,3) childPrintf(HANDLE h, const char* fmt, ...)
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
{
    va_list     valist;
    char        buffer[1024];
    DWORD       w;

    va_start(valist, fmt);
    vsprintf(buffer, fmt, valist);
    va_end(valist);
    WriteFile(h, buffer, strlen(buffer), &w, NULL);
}

static char* getChildString(const char* sect, const char* key)
{
    char        buf[1024];
    char*       ret;

    GetPrivateProfileStringA(sect, key, "-", buf, sizeof(buf), child_file);
    if (buf[0] == '\0' || (buf[0] == '-' && buf[1] == '\0')) return NULL;
    assert(!(strlen(buf) & 1));
    ret = decodeA(buf);
    return ret;
}


/***
 *
 * Child code
146 147
 *
 ***/
148

149
#define CHILD_DDE_TIMEOUT 2500
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
static DWORD ddeInst;
static HSZ hszTopic;
static char ddeExec[MAX_PATH], ddeApplication[MAX_PATH];
static BOOL post_quit_on_execute;

/* Handle DDE for doChild() and test_dde_default_app() */
static HDDEDATA CALLBACK ddeCb(UINT uType, UINT uFmt, HCONV hConv,
                               HSZ hsz1, HSZ hsz2, HDDEDATA hData,
                               ULONG_PTR dwData1, ULONG_PTR dwData2)
{
    DWORD size = 0;

    if (winetest_debug > 2)
        trace("dde_cb: %04x, %04x, %p, %p, %p, %p, %08lx, %08lx\n",
              uType, uFmt, hConv, hsz1, hsz2, hData, dwData1, dwData2);

    switch (uType)
    {
        case XTYP_CONNECT:
            if (!DdeCmpStringHandles(hsz1, hszTopic))
            {
                size = DdeQueryStringA(ddeInst, hsz2, ddeApplication, MAX_PATH, CP_WINANSI);
                ok(size < MAX_PATH, "got size %d\n", size);
                assert(size < MAX_PATH);
                return (HDDEDATA)TRUE;
            }
            return (HDDEDATA)FALSE;

        case XTYP_EXECUTE:
            size = DdeGetData(hData, (LPBYTE)ddeExec, MAX_PATH, 0);
            ok(size < MAX_PATH, "got size %d\n", size);
            assert(size < MAX_PATH);
            DdeFreeDataHandle(hData);
            if (post_quit_on_execute)
                PostQuitMessage(0);
            return (HDDEDATA)DDE_FACK;

        default:
            return NULL;
    }
}
191 192 193 194 195 196

static HANDLE hEvent;
static void init_event(const char* child_file)
{
    char* event_name;
    event_name=strrchr(child_file, '\\')+1;
197
    hEvent=CreateEventA(NULL, FALSE, FALSE, event_name);
198
}
199

200 201 202 203 204
/*
 * This is just to make sure the child won't run forever stuck in a
 * GetMessage() loop when DDE fails for some reason.
 */
static void CALLBACK childTimeout(HWND wnd, UINT msg, UINT_PTR timer, DWORD time)
205
{
206 207 208 209 210 211 212
    trace("childTimeout called\n");

    PostQuitMessage(0);
}

static void doChild(int argc, char** argv)
{
213
    char *filename, buffer[MAX_PATH];
214 215 216 217 218 219 220 221 222 223
    HANDLE hFile, map;
    int i;
    UINT_PTR timer;

    filename=argv[2];
    hFile=CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
    if (hFile == INVALID_HANDLE_VALUE)
        return;

    /* Arguments */
224
    childPrintf(hFile, "[Child]\r\n");
225
    if (winetest_debug > 2)
226
    {
227 228 229 230 231 232 233 234 235 236 237
        trace("cmdlineA='%s'\n", GetCommandLineA());
        trace("argcA=%d\n", argc);
    }
    childPrintf(hFile, "cmdlineA=%s\r\n", encodeA(GetCommandLineA()));
    childPrintf(hFile, "argcA=%d\r\n", argc);
    for (i = 0; i < argc; i++)
    {
        if (winetest_debug > 2)
            trace("argvA%d='%s'\n", i, argv[i]);
        childPrintf(hFile, "argvA%d=%s\r\n", i, encodeA(argv[i]));
    }
238 239 240 241 242 243 244 245 246
    GetModuleFileNameA(GetModuleHandleA(NULL), buffer, sizeof(buffer));
    childPrintf(hFile, "longPath=%s\r\n", encodeA(buffer));

    /* Check environment variable inheritance */
    *buffer = '\0';
    SetLastError(0);
    GetEnvironmentVariableA("ShlexecVar", buffer, sizeof(buffer));
    childPrintf(hFile, "ShlexecVarLE=%d\r\n", GetLastError());
    childPrintf(hFile, "ShlexecVar=%s\r\n", encodeA(buffer));
247 248 249 250

    map = OpenFileMappingA(FILE_MAP_READ, FALSE, "winetest_shlexec_dde_map");
    if (map != NULL)
    {
251 252
        HANDLE dde_ready;
        char *shared_block = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 4096);
253 254 255
        CloseHandle(map);
        if (shared_block[0] != '\0' || shared_block[1] != '\0')
        {
256 257 258 259 260
            HDDEDATA hdde;
            HSZ hszApplication;
            MSG msg;
            UINT rc;

261 262 263 264
            post_quit_on_execute = TRUE;
            ddeInst = 0;
            rc = DdeInitializeA(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES |
                                CBF_FAIL_POKES | CBF_FAIL_REQUESTS, 0);
265
            ok(rc == DMLERR_NO_ERROR, "DdeInitializeA() returned %d\n", rc);
266
            hszApplication = DdeCreateStringHandleA(ddeInst, shared_block, CP_WINANSI);
267 268 269 270 271 272
            ok(hszApplication != NULL, "DdeCreateStringHandleA(%s) = NULL\n", shared_block);
            shared_block += strlen(shared_block) + 1;
            hszTopic = DdeCreateStringHandleA(ddeInst, shared_block, CP_WINANSI);
            ok(hszTopic != NULL, "DdeCreateStringHandleA(%s) = NULL\n", shared_block);
            hdde = DdeNameService(ddeInst, hszApplication, 0, DNS_REGISTER | DNS_FILTEROFF);
            ok(hdde != NULL, "DdeNameService() failed le=%u\n", GetLastError());
273

274
            timer = SetTimer(NULL, 0, CHILD_DDE_TIMEOUT, childTimeout);
275 276 277 278 279 280

            dde_ready = OpenEventA(EVENT_MODIFY_STATE, FALSE, "winetest_shlexec_dde_ready");
            SetEvent(dde_ready);
            CloseHandle(dde_ready);

            while (GetMessageA(&msg, NULL, 0, 0))
281 282 283
            {
                if (winetest_debug > 2)
                    trace("msg %d lParam=%ld wParam=%lu\n", msg.message, msg.lParam, msg.wParam);
284
                DispatchMessageA(&msg);
285
            }
286 287 288

            Sleep(500);
            KillTimer(NULL, timer);
289 290 291 292 293
            hdde = DdeNameService(ddeInst, hszApplication, 0, DNS_UNREGISTER);
            ok(hdde != NULL, "DdeNameService() failed le=%u\n", GetLastError());
            ok(DdeFreeStringHandle(ddeInst, hszTopic), "DdeFreeStringHandle(topic)\n");
            ok(DdeFreeStringHandle(ddeInst, hszApplication), "DdeFreeStringHandle(application)\n");
            ok(DdeUninitialize(ddeInst), "DdeUninitialize() failed\n");
294 295 296 297 298 299 300 301 302 303 304
        }
        else
        {
            dde_ready = OpenEventA(EVENT_MODIFY_STATE, FALSE, "winetest_shlexec_dde_ready");
            SetEvent(dde_ready);
            CloseHandle(dde_ready);
        }

        UnmapViewOfFile(shared_block);

        childPrintf(hFile, "ddeExec=%s\r\n", encodeA(ddeExec));
305
    }
306

307
    childPrintf(hFile, "Failures=%d\r\n", winetest_get_failures());
308 309 310 311 312
    CloseHandle(hFile);

    init_event(filename);
    SetEvent(hEvent);
    CloseHandle(hEvent);
313 314
}

315 316 317 318 319 320 321 322
static void dump_child_(const char* file, int line)
{
    if (winetest_debug > 1)
    {
        char key[18];
        char* str;
        int i, c;

323
        str=getChildString("Child", "cmdlineA");
324
        trace_(file, line)("cmdlineA='%s'\n", str);
325
        c=GetPrivateProfileIntA("Child", "argcA", -1, child_file);
326 327 328 329
        trace_(file, line)("argcA=%d\n",c);
        for (i=0;i<c;i++)
        {
            sprintf(key, "argvA%d", i);
330
            str=getChildString("Child", key);
331 332
            trace_(file, line)("%s='%s'\n", key, str);
        }
333

334 335 336 337 338
        c=GetPrivateProfileIntA("Child", "ShlexecVarLE", -1, child_file);
        trace_(file, line)("ShlexecVarLE=%d\n", c);
        str=getChildString("Child", "ShlexecVar");
        trace_(file, line)("ShlexecVar='%s'\n", str);

339 340
        c=GetPrivateProfileIntA("Child", "Failures", -1, child_file);
        trace_(file, line)("Failures=%d\n", c);
341 342 343 344 345 346 347 348 349
    }
}


/***
 *
 * Helpers to check the ShellExecute() / child process results.
 *
 ***/
350

351 352 353 354
static char shell_call[2048];
static void WINETEST_PRINTF_ATTR(2,3) _okShell(int condition, const char *msg, ...)
{
    va_list valist;
355
    char buffer[2048];
356

357 358
    strcpy(buffer, shell_call);
    strcat(buffer, " ");
359
    va_start(valist, msg);
360
    vsprintf(buffer+strlen(buffer), msg, valist);
361
    va_end(valist);
362
    winetest_ok(condition, "%s", buffer);
363 364 365 366
}
#define okShell_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : _okShell
#define okShell okShell_(__FILE__, __LINE__)

367
static char assoc_desc[2048];
368
static void reset_association_description(void)
369 370 371
{
    *assoc_desc = '\0';
}
372

373
static void okChildString_(const char* file, int line, const char* key, const char* expected, const char* bad)
374
{
375
    char* result;
376
    result=getChildString("Child", key);
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
    if (!result)
    {
        okShell_(file, line)(FALSE, "%s expected '%s', but key not found or empty\n", key, expected);
        return;
    }
    okShell_(file, line)(lstrcmpiA(result, expected) == 0 ||
                         broken(lstrcmpiA(result, bad) == 0),
                         "%s expected '%s', got '%s'\n", key, expected, result);
}
#define okChildString(key, expected) okChildString_(__FILE__, __LINE__, (key), (expected), (expected))
#define okChildStringBroken(key, expected, broken) okChildString_(__FILE__, __LINE__, (key), (expected), (broken))

static int StrCmpPath(const char* s1, const char* s2)
{
    if (!s1 && !s2) return 0;
    if (!s2) return 1;
    if (!s1) return -1;
    while (*s1)
    {
        if (!*s2)
        {
            if (*s1=='.')
                s1++;
            return (*s1-*s2);
        }
        if ((*s1=='/' || *s1=='\\') && (*s2=='/' || *s2=='\\'))
        {
            while (*s1=='/' || *s1=='\\')
                s1++;
            while (*s2=='/' || *s2=='\\')
                s2++;
        }
        else if (toupper(*s1)==toupper(*s2))
        {
            s1++;
            s2++;
        }
        else
        {
            return (*s1-*s2);
        }
    }
    if (*s2=='.')
        s2++;
    if (*s2)
        return -1;
    return 0;
}

static void okChildPath_(const char* file, int line, const char* key, const char* expected)
{
    char* result;
429
    int equal, shortequal;
430
    result=getChildString("Child", key);
431 432 433 434 435
    if (!result)
    {
        okShell_(file,line)(FALSE, "%s expected '%s', but key not found or empty\n", key, expected);
        return;
    }
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
    shortequal = FALSE;
    equal = (StrCmpPath(result, expected) == 0);
    if (!equal)
    {
        char altpath[MAX_PATH];
        DWORD rc = GetLongPathNameA(expected, altpath, sizeof(altpath));
        if (0 < rc && rc < sizeof(altpath))
            equal = (StrCmpPath(result, altpath) == 0);
        if (!equal)
        {
            rc = GetShortPathNameA(expected, altpath, sizeof(altpath));
            if (0 < rc && rc < sizeof(altpath))
                shortequal = (StrCmpPath(result, altpath) == 0);
        }
    }
    okShell_(file,line)(equal || broken(shortequal) /* XP SP1 */,
452 453 454 455 456 457 458
                        "%s expected '%s', got '%s'\n", key, expected, result);
}
#define okChildPath(key, expected) okChildPath_(__FILE__, __LINE__, (key), (expected))

static void okChildInt_(const char* file, int line, const char* key, int expected)
{
    INT result;
459
    result=GetPrivateProfileIntA("Child", key, expected, child_file);
460 461 462 463 464 465 466 467
    okShell_(file,line)(result == expected,
                        "%s expected %d, but got %d\n", key, expected, result);
}
#define okChildInt(key, expected) okChildInt_(__FILE__, __LINE__, (key), (expected))

static void okChildIntBroken_(const char* file, int line, const char* key, int expected)
{
    INT result;
468
    result=GetPrivateProfileIntA("Child", key, expected, child_file);
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
    okShell_(file,line)(result == expected || broken(result != expected),
                        "%s expected %d, but got %d\n", key, expected, result);
}
#define okChildIntBroken(key, expected) okChildIntBroken_(__FILE__, __LINE__, (key), (expected))


/***
 *
 * ShellExecute wrappers
 *
 ***/

static void strcat_param(char* str, const char* name, const char* param)
{
    if (param)
    {
        if (str[strlen(str)-1] == '"')
            strcat(str, ", ");
        strcat(str, name);
        strcat(str, "=\"");
        strcat(str, param);
        strcat(str, "\"");
    }
}

static int _todo_wait = 0;
#define todo_wait for (_todo_wait = 1; _todo_wait; _todo_wait = 0)

static int bad_shellexecute = 0;

static INT_PTR shell_execute_(const char* file, int line, LPCSTR verb, LPCSTR filename, LPCSTR parameters, LPCSTR directory)
{
    INT_PTR rc, rcEmpty = 0;

    if(!verb)
        rcEmpty = shell_execute_(file, line, "", filename, parameters, directory);

506
    strcpy(shell_call, "ShellExecute(");
507
    strcat_param(shell_call, "verb", verb);
508
    strcat_param(shell_call, "file", filename);
509 510
    strcat_param(shell_call, "params", parameters);
    strcat_param(shell_call, "dir", directory);
511
    strcat(shell_call, ")");
512
    strcat(shell_call, assoc_desc);
513
    if (winetest_debug > 1)
514
        trace_(file, line)("Called %s\n", shell_call);
515

516
    DeleteFileA(child_file);
517
    SetLastError(0xcafebabe);
518

519 520 521 522
    /* FIXME: We cannot use ShellExecuteEx() here because if there is no
     * association it displays the 'Open With' dialog and I could not find
     * a flag to prevent this.
     */
523
    rc=(INT_PTR)ShellExecuteA(NULL, verb, filename, parameters, directory, SW_HIDE);
524

525
    if (rc > 32)
526 527
    {
        int wait_rc;
528
        wait_rc=WaitForSingleObject(hEvent, 5000);
529 530 531
        if (wait_rc == WAIT_TIMEOUT)
        {
            HWND wnd = FindWindowA("#32770", "Windows");
532 533
            if (!wnd)
                wnd = FindWindowA("Shell_Flyout", "");
534 535
            if (wnd != NULL)
            {
536
                SendMessageA(wnd, WM_CLOSE, 0, 0);
537 538 539 540 541
                win_skip("Skipping shellexecute of file with unassociated extension\n");
                skip_noassoc_tests = TRUE;
                rc = SE_ERR_NOASSOC;
            }
        }
542
        todo_wine_if(_todo_wait)
543 544
            okShell_(file, line)(wait_rc==WAIT_OBJECT_0 || rc <= 32,
                                 "WaitForSingleObject returned %d\n", wait_rc);
545 546 547 548 549
    }
    /* The child process may have changed the result file, so let profile
     * functions know about it
     */
    WritePrivateProfileStringA(NULL, NULL, NULL, child_file);
550 551
    if (GetFileAttributesA(child_file) != INVALID_FILE_ATTRIBUTES)
    {
552 553 554
        int c;
        dump_child_(file, line);
        c = GetPrivateProfileIntA("Child", "Failures", -1, child_file);
555 556
        if (c > 0)
            winetest_add_failures(c);
557 558
        okChildInt_(file, line, "ShlexecVarLE", 0);
        okChildString_(file, line, "ShlexecVar", "Present", "Present");
559
    }
560

561
    if(!verb)
562 563 564
    {
        if (rc != rcEmpty && rcEmpty == SE_ERR_NOASSOC) /* NT4 */
            bad_shellexecute = 1;
565 566 567
        okShell_(file, line)(rc == rcEmpty ||
                             broken(rc != rcEmpty && rcEmpty == SE_ERR_NOASSOC) /* NT4 */,
                             "Got different return value with empty string: %lu %lu\n", rc, rcEmpty);
568
    }
569

570
    return rc;
571
}
572 573
#define shell_execute(verb, filename, parameters, directory) \
    shell_execute_(__FILE__, __LINE__, verb, filename, parameters, directory)
574

575 576 577 578
static INT_PTR shell_execute_ex_(const char* file, int line,
                                 DWORD mask, LPCSTR verb, LPCSTR filename,
                                 LPCSTR parameters, LPCSTR directory,
                                 LPCSTR class)
579
{
580
    char smask[11];
581
    SHELLEXECUTEINFOA sei;
582
    BOOL success;
583
    INT_PTR rc;
584

585 586 587
    /* Add some flags so we can wait for the child process */
    mask |= SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;

588
    strcpy(shell_call, "ShellExecuteEx(");
589 590
    sprintf(smask, "0x%x", mask);
    strcat_param(shell_call, "mask", smask);
591
    strcat_param(shell_call, "verb", verb);
592
    strcat_param(shell_call, "file", filename);
593 594
    strcat_param(shell_call, "params", parameters);
    strcat_param(shell_call, "dir", directory);
595
    strcat_param(shell_call, "class", class);
596
    strcat(shell_call, ")");
597
    strcat(shell_call, assoc_desc);
598
    if (winetest_debug > 1)
599
        trace_(file, line)("Called %s\n", shell_call);
600 601

    sei.cbSize=sizeof(sei);
602
    sei.fMask=mask;
603
    sei.hwnd=NULL;
604
    sei.lpVerb=verb;
605
    sei.lpFile=filename;
606 607 608 609 610
    sei.lpParameters=parameters;
    sei.lpDirectory=directory;
    sei.nShow=SW_SHOWNORMAL;
    sei.hInstApp=NULL; /* Out */
    sei.lpIDList=NULL;
611
    sei.lpClass=class;
612 613 614
    sei.hkeyClass=NULL;
    sei.dwHotKey=0;
    U(sei).hIcon=NULL;
615
    sei.hProcess=(HANDLE)0xdeadbeef; /* Out */
616

617
    DeleteFileA(child_file);
618
    SetLastError(0xcafebabe);
619
    success=ShellExecuteExA(&sei);
620
    rc=(INT_PTR)sei.hInstApp;
621 622 623
    okShell_(file, line)((success && rc > 32) || (!success && rc <= 32),
                         "rc=%d and hInstApp=%ld is not allowed\n",
                         success, rc);
624

625
    if (rc > 32)
626
    {
627
        DWORD wait_rc, rc;
628 629
        if (sei.hProcess!=NULL)
        {
630
            wait_rc=WaitForSingleObject(sei.hProcess, 5000);
631 632 633
            okShell_(file, line)(wait_rc==WAIT_OBJECT_0,
                                 "WaitForSingleObject(hProcess) returned %d\n",
                                 wait_rc);
634 635
            wait_rc = GetExitCodeProcess(sei.hProcess, &rc);
            okShell_(file, line)(wait_rc, "GetExitCodeProcess() failed le=%u\n", GetLastError());
636
            todo_wine_if(_todo_wait)
637 638
                okShell_(file, line)(rc == 0, "child returned %u\n", rc);
            CloseHandle(sei.hProcess);
639
        }
640
        wait_rc=WaitForSingleObject(hEvent, 5000);
641
        todo_wine_if(_todo_wait)
642 643
            okShell_(file, line)(wait_rc==WAIT_OBJECT_0,
                                 "WaitForSingleObject returned %d\n", wait_rc);
644
    }
645
    else
646 647
        okShell_(file, line)(sei.hProcess==NULL,
                             "returned a process handle %p\n", sei.hProcess);
648

649 650 651 652
    /* The child process may have changed the result file, so let profile
     * functions know about it
     */
    WritePrivateProfileStringA(NULL, NULL, NULL, child_file);
653
    if (rc > 32 && GetFileAttributesA(child_file) != INVALID_FILE_ATTRIBUTES)
654
    {
655 656 657
        int c;
        dump_child_(file, line);
        c = GetPrivateProfileIntA("Child", "Failures", -1, child_file);
658 659
        if (c > 0)
            winetest_add_failures(c);
660 661 662 663 664 665 666 667 668 669 670 671 672
        /* When NOZONECHECKS is specified the environment variables are not
         * inherited if the process does not have elevated privileges.
         */
        if ((mask & SEE_MASK_NOZONECHECKS) && skip_shlexec_tests)
        {
            okChildInt_(file, line, "ShlexecVarLE", 203);
            okChildString_(file, line, "ShlexecVar", "", "");
        }
        else
        {
            okChildInt_(file, line, "ShlexecVarLE", 0);
            okChildString_(file, line, "ShlexecVar", "Present", "Present");
        }
673
    }
674

675 676
    return rc;
}
677 678
#define shell_execute_ex(mask, verb, filename, parameters, directory, class) \
    shell_execute_ex_(__FILE__, __LINE__, mask, verb, filename, parameters, directory, class)
679 680 681 682 683 684 685 686


/***
 *
 * Functions to create / delete associations wrappers
 *
 ***/

687
static BOOL create_test_class(const char* class, BOOL protocol)
688 689
{
    HKEY hkey, hkey_shell;
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
    LONG rc;

    rc = RegCreateKeyExA(HKEY_CLASSES_ROOT, class, 0, NULL, 0,
                         KEY_CREATE_SUB_KEY | KEY_SET_VALUE, NULL,
                         &hkey, NULL);
    ok(rc == ERROR_SUCCESS || rc == ERROR_ACCESS_DENIED,
       "could not create class %s (rc=%d)\n", class, rc);
    if (rc != ERROR_SUCCESS)
        return FALSE;

    if (protocol)
    {
        rc = RegSetValueExA(hkey, "URL Protocol", 0, REG_SZ, (LPBYTE)"", 1);
        ok(rc == ERROR_SUCCESS, "RegSetValueEx '%s' failed, expected ERROR_SUCCESS, got %d\n", class, rc);
    }

    rc = RegCreateKeyExA(hkey, "shell", 0, NULL, 0,
                         KEY_CREATE_SUB_KEY, NULL, &hkey_shell, NULL);
    ok(rc == ERROR_SUCCESS, "RegCreateKeyEx 'shell' failed, expected ERROR_SUCCESS, got %d\n", rc);

    CloseHandle(hkey);
    CloseHandle(hkey_shell);
    return TRUE;
}

static BOOL create_test_association(const char* extension)
{
    HKEY hkey;
718 719 720 721
    char class[MAX_PATH];
    LONG rc;

    sprintf(class, "shlexec%s", extension);
722 723
    rc=RegCreateKeyExA(HKEY_CLASSES_ROOT, extension, 0, NULL, 0, KEY_SET_VALUE,
                       NULL, &hkey, NULL);
724 725
    ok(rc == ERROR_SUCCESS || rc == ERROR_ACCESS_DENIED,
       "could not create association %s (rc=%d)\n", class, rc);
726 727 728
    if (rc != ERROR_SUCCESS)
        return FALSE;

729
    rc=RegSetValueExA(hkey, NULL, 0, REG_SZ, (LPBYTE) class, strlen(class)+1);
730
    ok(rc==ERROR_SUCCESS, "RegSetValueEx '%s' failed, expected ERROR_SUCCESS, got %d\n", class, rc);
731 732
    CloseHandle(hkey);

733
    return create_test_class(class, FALSE);
734 735
}

736 737 738 739 740
/* Based on RegDeleteTreeW from dlls/advapi32/registry.c */
static LSTATUS myRegDeleteTreeA(HKEY hKey, LPCSTR lpszSubKey)
{
    LONG ret;
    DWORD dwMaxSubkeyLen, dwMaxValueLen;
741 742 743
    DWORD dwMaxLen, dwSize;
    CHAR szNameBuf[MAX_PATH], *lpszName = szNameBuf;
    HKEY hSubKey = hKey;
744

745 746 747 748 749
    if(lpszSubKey)
    {
        ret = RegOpenKeyExA(hKey, lpszSubKey, 0, KEY_READ, &hSubKey);
        if (ret) return ret;
    }
750

751 752 753 754
    /* Get highest length for keys, values */
    ret = RegQueryInfoKeyA(hSubKey, NULL, NULL, NULL, NULL,
            &dwMaxSubkeyLen, NULL, NULL, &dwMaxValueLen, NULL, NULL, NULL);
    if (ret) goto cleanup;
755

756 757 758 759 760 761 762
    dwMaxSubkeyLen++;
    dwMaxValueLen++;
    dwMaxLen = max(dwMaxSubkeyLen, dwMaxValueLen);
    if (dwMaxLen > sizeof(szNameBuf)/sizeof(CHAR))
    {
        /* Name too big: alloc a buffer for it */
        if (!(lpszName = HeapAlloc( GetProcessHeap(), 0, dwMaxLen*sizeof(CHAR))))
763
        {
764 765
            ret = ERROR_NOT_ENOUGH_MEMORY;
            goto cleanup;
766
        }
767
    }
768 769


770 771 772 773 774 775 776 777 778
    /* Recursively delete all the subkeys */
    while (TRUE)
    {
        dwSize = dwMaxLen;
        if (RegEnumKeyExA(hSubKey, 0, lpszName, &dwSize, NULL,
                          NULL, NULL, NULL)) break;

        ret = myRegDeleteTreeA(hSubKey, lpszName);
        if (ret) goto cleanup;
779 780
    }

781 782 783 784 785 786 787 788
    if (lpszSubKey)
        ret = RegDeleteKeyA(hKey, lpszSubKey);
    else
        while (TRUE)
        {
            dwSize = dwMaxLen;
            if (RegEnumValueA(hKey, 0, lpszName, &dwSize,
                  NULL, NULL, NULL, NULL)) break;
789

790 791 792 793 794 795 796 797 798 799 800
            ret = RegDeleteValueA(hKey, lpszName);
            if (ret) goto cleanup;
        }

cleanup:
    /* Free buffer if allocated */
    if (lpszName != szNameBuf)
        HeapFree( GetProcessHeap(), 0, lpszName);
    if(lpszSubKey)
        RegCloseKey(hSubKey);
    return ret;
801 802
}

803 804 805 806 807
static void delete_test_class(const char* classname)
{
    myRegDeleteTreeA(HKEY_CLASSES_ROOT, classname);
}

808
static void delete_test_association(const char* extension)
809
{
810
    char classname[MAX_PATH];
811

812 813
    sprintf(classname, "shlexec%s", extension);
    delete_test_class(classname);
814
    myRegDeleteTreeA(HKEY_CLASSES_ROOT, extension);
815 816
}

817
static void create_test_verb_dde(const char* classname, const char* verb,
818 819 820
                                 int rawcmd, const char* cmdtail, const char *ddeexec,
                                 const char *application, const char *topic,
                                 const char *ifexec)
821
{
822 823 824 825
    HKEY hkey_shell, hkey_verb, hkey_cmd;
    char shell[MAX_PATH];
    char* cmd;
    LONG rc;
826

827
    strcpy(assoc_desc, " Assoc ");
828
    strcat_param(assoc_desc, "class", classname);
829 830 831 832 833 834 835 836 837
    strcat_param(assoc_desc, "verb", verb);
    sprintf(shell, "%d", rawcmd);
    strcat_param(assoc_desc, "rawcmd", shell);
    strcat_param(assoc_desc, "cmdtail", cmdtail);
    strcat_param(assoc_desc, "ddeexec", ddeexec);
    strcat_param(assoc_desc, "app", application);
    strcat_param(assoc_desc, "topic", topic);
    strcat_param(assoc_desc, "ifexec", ifexec);

838
    sprintf(shell, "%s\\shell", classname);
839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861
    rc=RegOpenKeyExA(HKEY_CLASSES_ROOT, shell, 0,
                     KEY_CREATE_SUB_KEY, &hkey_shell);
    ok(rc == ERROR_SUCCESS, "%s key creation failed with %d\n", shell, rc);

    rc=RegCreateKeyExA(hkey_shell, verb, 0, NULL, 0, KEY_CREATE_SUB_KEY,
                       NULL, &hkey_verb, NULL);
    ok(rc == ERROR_SUCCESS, "%s verb key creation failed with %d\n", verb, rc);

    rc=RegCreateKeyExA(hkey_verb, "command", 0, NULL, 0, KEY_SET_VALUE,
                       NULL, &hkey_cmd, NULL);
    ok(rc == ERROR_SUCCESS, "\'command\' key creation failed with %d\n", rc);

    if (rawcmd)
    {
        rc=RegSetValueExA(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmdtail, strlen(cmdtail)+1);
    }
    else
    {
        cmd=HeapAlloc(GetProcessHeap(), 0, strlen(argv0)+10+strlen(child_file)+2+strlen(cmdtail)+1);
        sprintf(cmd,"%s shlexec \"%s\" %s", argv0, child_file, cmdtail);
        rc=RegSetValueExA(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmd, strlen(cmd)+1);
        ok(rc == ERROR_SUCCESS, "setting command failed with %d\n", rc);
        HeapFree(GetProcessHeap(), 0, cmd);
862 863
    }

864
    if (ddeexec)
865
    {
866 867 868 869 870 871 872 873 874 875
        HKEY hkey_ddeexec, hkey_application, hkey_topic, hkey_ifexec;

        rc=RegCreateKeyExA(hkey_verb, "ddeexec", 0, NULL, 0, KEY_SET_VALUE |
                           KEY_CREATE_SUB_KEY, NULL, &hkey_ddeexec, NULL);
        ok(rc == ERROR_SUCCESS, "\'ddeexec\' key creation failed with %d\n", rc);
        rc=RegSetValueExA(hkey_ddeexec, NULL, 0, REG_SZ, (LPBYTE)ddeexec,
                          strlen(ddeexec)+1);
        ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);

        if (application)
876
        {
877 878 879 880 881 882 883 884
            rc=RegCreateKeyExA(hkey_ddeexec, "application", 0, NULL, 0, KEY_SET_VALUE,
                               NULL, &hkey_application, NULL);
            ok(rc == ERROR_SUCCESS, "\'application\' key creation failed with %d\n", rc);

            rc=RegSetValueExA(hkey_application, NULL, 0, REG_SZ, (LPBYTE)application,
                              strlen(application)+1);
            ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
            CloseHandle(hkey_application);
885
        }
886
        if (topic)
887
        {
888 889 890 891 892 893 894
            rc=RegCreateKeyExA(hkey_ddeexec, "topic", 0, NULL, 0, KEY_SET_VALUE,
                               NULL, &hkey_topic, NULL);
            ok(rc == ERROR_SUCCESS, "\'topic\' key creation failed with %d\n", rc);
            rc=RegSetValueExA(hkey_topic, NULL, 0, REG_SZ, (LPBYTE)topic,
                              strlen(topic)+1);
            ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
            CloseHandle(hkey_topic);
895
        }
896
        if (ifexec)
897
        {
898 899 900 901 902 903 904
            rc=RegCreateKeyExA(hkey_ddeexec, "ifexec", 0, NULL, 0, KEY_SET_VALUE,
                               NULL, &hkey_ifexec, NULL);
            ok(rc == ERROR_SUCCESS, "\'ifexec\' key creation failed with %d\n", rc);
            rc=RegSetValueExA(hkey_ifexec, NULL, 0, REG_SZ, (LPBYTE)ifexec,
                              strlen(ifexec)+1);
            ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
            CloseHandle(hkey_ifexec);
905
        }
906
        CloseHandle(hkey_ddeexec);
907 908
    }

909 910 911
    CloseHandle(hkey_shell);
    CloseHandle(hkey_verb);
    CloseHandle(hkey_cmd);
912 913
}

914 915 916 917
/* Creates a class' non-DDE test verb.
 * This function is meant to be used to create long term test verbs and thus
 * does not trace them.
 */
918
static void create_test_verb(const char* classname, const char* verb,
919
                             int rawcmd, const char* cmdtail)
920
{
921
    create_test_verb_dde(classname, verb, rawcmd, cmdtail, NULL, NULL,
922 923
                         NULL, NULL);
    reset_association_description();
924 925
}

926

927 928 929 930 931 932 933 934 935 936 937 938 939 940
/***
 *
 * GetLongPathNameA equivalent that supports Win95 and WinNT
 *
 ***/

static DWORD get_long_path_name(const char* shortpath, char* longpath, DWORD longlen)
{
    char tmplongpath[MAX_PATH];
    const char* p;
    DWORD sp = 0, lp = 0;
    DWORD tmplen;
    WIN32_FIND_DATAA wfd;
    HANDLE goit;
941

942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974
    if (!shortpath || !shortpath[0])
        return 0;

    if (shortpath[1] == ':')
    {
        tmplongpath[0] = shortpath[0];
        tmplongpath[1] = ':';
        lp = sp = 2;
    }

    while (shortpath[sp])
    {
        /* check for path delimiters and reproduce them */
        if (shortpath[sp] == '\\' || shortpath[sp] == '/')
        {
            if (!lp || tmplongpath[lp-1] != '\\')
            {
                /* strip double "\\" */
                tmplongpath[lp++] = '\\';
            }
            tmplongpath[lp] = 0; /* terminate string */
            sp++;
            continue;
        }

        p = shortpath + sp;
        if (sp == 0 && p[0] == '.' && (p[1] == '/' || p[1] == '\\'))
        {
            tmplongpath[lp++] = *p++;
            tmplongpath[lp++] = *p++;
        }
        for (; *p && *p != '/' && *p != '\\'; p++);
        tmplen = p - (shortpath + sp);
975
        lstrcpynA(tmplongpath + lp, shortpath + sp, tmplen + 1);
976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999
        /* Check if the file exists and use the existing file name */
        goit = FindFirstFileA(tmplongpath, &wfd);
        if (goit == INVALID_HANDLE_VALUE)
            return 0;
        FindClose(goit);
        strcpy(tmplongpath + lp, wfd.cFileName);
        lp += strlen(tmplongpath + lp);
        sp += tmplen;
    }
    tmplen = strlen(shortpath) - 1;
    if ((shortpath[tmplen] == '/' || shortpath[tmplen] == '\\') &&
        (tmplongpath[lp - 1] != '/' && tmplongpath[lp - 1] != '\\'))
        tmplongpath[lp++] = shortpath[tmplen];
    tmplongpath[lp] = 0;

    tmplen = strlen(tmplongpath) + 1;
    if (tmplen <= longlen)
    {
        strcpy(longpath, tmplongpath);
        tmplen--; /* length without 0 */
    }

    return tmplen;
}
1000

1001

1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
/***
 *
 * Tests
 *
 ***/

static const char* testfiles[]=
{
    "%s\\test file.shlexec",
    "%s\\%%nasty%% $file.shlexec",
    "%s\\test file.noassoc",
    "%s\\test file.noassoc.shlexec",
    "%s\\test file.shlexec.noassoc",
    "%s\\test_shortcut_shlexec.lnk",
    "%s\\test_shortcut_exe.lnk",
1017 1018
    "%s\\test file.shl",
    "%s\\test file.shlfoo",
1019
    "%s\\test file.sha",
1020
    "%s\\test file.sfe",
1021
    "%s\\test file.shlproto",
1022 1023
    "%s\\masked file.shlexec",
    "%s\\masked",
1024
    "%s\\test file.sde",
1025 1026
    "%s\\test file.exe",
    "%s\\test2.exe",
1027 1028 1029 1030
    "%s\\simple.shlexec",
    "%s\\drawback_file.noassoc",
    "%s\\drawback_file.noassoc foo.shlexec",
    "%s\\drawback_nonexist.noassoc foo.shlexec",
1031 1032 1033
    NULL
};

1034 1035
typedef struct
{
1036 1037
    const char* verb;
    const char* basename;
1038
    int todo;
1039
    INT_PTR rc;
1040 1041 1042 1043 1044
} filename_tests_t;

static filename_tests_t filename_tests[]=
{
    /* Test bad / nonexistent filenames */
1045 1046
    {NULL,           "%s\\nonexistent.shlexec", 0x0, SE_ERR_FNF},
    {NULL,           "%s\\nonexistent.noassoc", 0x0, SE_ERR_FNF},
1047 1048

    /* Standard tests */
1049 1050 1051 1052
    {NULL,           "%s\\test file.shlexec",   0x0, 33},
    {NULL,           "%s\\test file.shlexec.",  0x0, 33},
    {NULL,           "%s\\%%nasty%% $file.shlexec", 0x0, 33},
    {NULL,           "%s/test file.shlexec",    0x0, 33},
1053 1054

    /* Test filenames with no association */
1055
    {NULL,           "%s\\test file.noassoc",   0x0,  SE_ERR_NOASSOC},
1056 1057

    /* Test double extensions */
1058
    {NULL,           "%s\\test file.noassoc.shlexec", 0x0, 33},
1059
    {NULL,           "%s\\test file.shlexec.noassoc", 0x0, SE_ERR_NOASSOC},
1060

1061
    /* Test alternate verbs */
1062
    {"LowerL",       "%s\\nonexistent.shlexec", 0x0, SE_ERR_FNF},
1063
    {"LowerL",       "%s\\test file.noassoc",   0x0,  SE_ERR_NOASSOC},
1064

1065 1066
    {"QuotedLowerL", "%s\\test file.shlexec",   0x0, 33},
    {"QuotedUpperL", "%s\\test file.shlexec",   0x0, 33},
1067

1068 1069
    {"notaverb",     "%s\\test file.shlexec",   0x10, SE_ERR_NOASSOC},

1070 1071
    {"averb",        "%s\\test file.sha",       0x10, 33},

1072
    /* Test file masked due to space */
1073
    {NULL,           "%s\\masked file.shlexec",   0x0, 33},
1074 1075
    /* Test if quoting prevents the masking */
    {NULL,           "%s\\masked file.shlexec",   0x40, 33},
1076 1077
    /* Test with incorrect quote */
    {NULL,           "\"%s\\masked file.shlexec",   0x0, SE_ERR_FNF},
1078

1079 1080 1081
    /* Test extension / URI protocol collision */
    {NULL,           "%s\\test file.shlproto",   0x0, SE_ERR_NOASSOC},

1082
    {NULL, NULL, 0}
1083 1084 1085 1086 1087
};

static filename_tests_t noquotes_tests[]=
{
    /* Test unquoted '%1' thingies */
1088 1089 1090
    {"NoQuotes",     "%s\\test file.shlexec",   0xa, 33},
    {"LowerL",       "%s\\test file.shlexec",   0xa, 33},
    {"UpperL",       "%s\\test file.shlexec",   0xa, 33},
1091

1092
    {NULL, NULL, 0}
1093 1094
};

1095 1096 1097
static void test_lpFile_parsed(void)
{
    char fileA[MAX_PATH];
1098
    INT_PTR rc;
1099

1100 1101 1102 1103 1104 1105
    if (skip_shlexec_tests)
    {
        skip("No filename parsing tests due to lack of .shlexec association\n");
        return;
    }

1106
    /* existing "drawback_file.noassoc" prevents finding "drawback_file.noassoc foo.shlexec" on wine */
1107
    sprintf(fileA, "%s\\drawback_file.noassoc foo.shlexec", tmpdir);
1108
    rc=shell_execute(NULL, fileA, NULL, NULL);
1109
    okShell(rc > 32, "failed: rc=%lu\n", rc);
1110 1111

    /* if quoted, existing "drawback_file.noassoc" not prevents finding "drawback_file.noassoc foo.shlexec" on wine */
1112
    sprintf(fileA, "\"%s\\drawback_file.noassoc foo.shlexec\"", tmpdir);
1113
    rc=shell_execute(NULL, fileA, NULL, NULL);
1114 1115
    okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win95/NT4 */,
            "failed: rc=%lu\n", rc);
1116 1117 1118

    /* error should be SE_ERR_FNF, not SE_ERR_NOASSOC */
    sprintf(fileA, "\"%s\\drawback_file.noassoc\" foo.shlexec", tmpdir);
1119
    rc=shell_execute(NULL, fileA, NULL, NULL);
1120
    okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
1121 1122

    /* ""command"" not works on wine (and real win9x and w2k) */
1123
    sprintf(fileA, "\"\"%s\\simple.shlexec\"\"", tmpdir);
1124
    rc=shell_execute(NULL, fileA, NULL, NULL);
1125 1126
    todo_wine okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win9x/2000 */,
                      "failed: rc=%lu\n", rc);
1127 1128

    /* nonexisting "drawback_nonexist.noassoc" not prevents finding "drawback_nonexist.noassoc foo.shlexec" on wine */
1129
    sprintf(fileA, "%s\\drawback_nonexist.noassoc foo.shlexec", tmpdir);
1130
    rc=shell_execute(NULL, fileA, NULL, NULL);
1131
    okShell(rc > 32, "failed: rc=%lu\n", rc);
1132 1133

    /* is SEE_MASK_DOENVSUBST default flag? Should only be when XP emulates 9x (XP bug or real 95 or ME behavior ?) */
1134
    rc=shell_execute(NULL, "%TMPDIR%\\simple.shlexec", NULL, NULL);
1135
    todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
1136 1137

    /* quoted */
1138
    rc=shell_execute(NULL, "\"%TMPDIR%\\simple.shlexec\"", NULL, NULL);
1139
    todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
1140 1141

    /* test SEE_MASK_DOENVSUBST works */
1142
    rc=shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI,
1143
                        NULL, "%TMPDIR%\\simple.shlexec", NULL, NULL, NULL);
1144
    okShell(rc > 32, "failed: rc=%lu\n", rc);
1145

1146
    /* quoted lpFile does not work on real win95 and nt4 */
1147
    rc=shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI,
1148
                        NULL, "\"%TMPDIR%\\simple.shlexec\"", NULL, NULL, NULL);
1149 1150
    okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win95/NT4 */,
            "failed: rc=%lu\n", rc);
1151 1152
}

1153 1154 1155 1156 1157 1158 1159 1160 1161
typedef struct
{
    const char* cmd;
    const char* args[11];
    int todo;
} cmdline_tests_t;

static const cmdline_tests_t cmdline_tests[] =
{
1162 1163 1164
    {"exe",
     {"exe", NULL}, 0},

1165 1166 1167 1168 1169 1170 1171 1172 1173
    {"exe arg1 arg2 \"arg three\" 'four five` six\\ $even)",
     {"exe", "arg1", "arg2", "arg three", "'four", "five`", "six\\", "$even)", NULL}, 0},

    {"exe arg=1 arg-2 three\tfour\rfour\nfour ",
     {"exe", "arg=1", "arg-2", "three", "four\rfour\nfour", NULL}, 0},

    {"exe arg\"one\" \"second\"arg thirdarg ",
     {"exe", "argone", "secondarg", "thirdarg", NULL}, 0},

1174 1175 1176 1177 1178 1179 1180
    /* Don't lose unclosed quoted arguments */
    {"exe arg1 \"unclosed",
     {"exe", "arg1", "unclosed", NULL}, 0},

    {"exe arg1 \"",
     {"exe", "arg1", "", NULL}, 0},

1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215
    /* cmd's metacharacters have no special meaning */
    {"exe \"one^\" \"arg\"&two three|four",
     {"exe", "one^", "arg&two", "three|four", NULL}, 0},

    /* Environment variables are not interpreted either */
    {"exe %TMPDIR% %2",
     {"exe", "%TMPDIR%", "%2", NULL}, 0},

    /* If not followed by a quote, backslashes go through as is */
    {"exe o\\ne t\\\\wo t\\\\\\ree f\\\\\\\\our ",
     {"exe", "o\\ne", "t\\\\wo", "t\\\\\\ree", "f\\\\\\\\our", NULL}, 0},

    {"exe \"o\\ne\" \"t\\\\wo\" \"t\\\\\\ree\" \"f\\\\\\\\our\" ",
     {"exe", "o\\ne", "t\\\\wo", "t\\\\\\ree", "f\\\\\\\\our", NULL}, 0},

    /* When followed by a quote their number is halved and the remainder
     * escapes the quote
     */
    {"exe \\\"one \\\\\"two\" \\\\\\\"three \\\\\\\\\"four\" end",
     {"exe", "\"one", "\\two", "\\\"three", "\\\\four", "end", NULL}, 0},

    {"exe \"one\\\" still\" \"two\\\\\" \"three\\\\\\\" still\" \"four\\\\\\\\\" end",
     {"exe", "one\" still", "two\\", "three\\\" still", "four\\\\", "end", NULL}, 0},

    /* One can put a quote in an unquoted string by tripling it, that is in
     * effect quoting it like so """ -> ". The general rule is as follows:
     * 3n   quotes -> n quotes
     * 3n+1 quotes -> n quotes plus start of a quoted string
     * 3n+2 quotes -> n quotes (plus an empty string from the remaining pair)
     * Nicely, when n is 0 we get the standard rules back.
     */
    {"exe two\"\"quotes next",
     {"exe", "twoquotes", "next", NULL}, 0},

    {"exe three\"\"\"quotes next",
1216
     {"exe", "three\"quotes", "next", NULL}, 0},
1217 1218

    {"exe four\"\"\"\" quotes\" next 4%3=1",
1219
     {"exe", "four\" quotes", "next", "4%3=1", NULL}, 0},
1220 1221

    {"exe five\"\"\"\"\"quotes next",
1222
     {"exe", "five\"quotes", "next", NULL}, 0},
1223 1224

    {"exe six\"\"\"\"\"\"quotes next",
1225
     {"exe", "six\"\"quotes", "next", NULL}, 0},
1226 1227

    {"exe seven\"\"\"\"\"\"\" quotes\" next 7%3=1",
1228
     {"exe", "seven\"\" quotes", "next", "7%3=1", NULL}, 0},
1229 1230

    {"exe twelve\"\"\"\"\"\"\"\"\"\"\"\"quotes next",
1231
     {"exe", "twelve\"\"\"\"quotes", "next", NULL}, 0},
1232 1233

    {"exe thirteen\"\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1",
1234
     {"exe", "thirteen\"\"\"\" quotes", "next", "13%3=1", NULL}, 0},
1235 1236 1237 1238 1239 1240 1241 1242

    /* Inside a quoted string the opening quote is added to the set of
     * consecutive quotes to get the effective quotes count. This gives:
     * 1+3n   quotes -> n quotes
     * 1+3n+1 quotes -> n quotes plus closes the quoted string
     * 1+3n+2 quotes -> n+1 quotes plus closes the quoted string
     */
    {"exe \"two\"\"quotes next",
1243
     {"exe", "two\"quotes", "next", NULL}, 0},
1244 1245

    {"exe \"two\"\" next",
1246
     {"exe", "two\"", "next", NULL}, 0},
1247 1248

    {"exe \"three\"\"\" quotes\" next 4%3=1",
1249
     {"exe", "three\" quotes", "next", "4%3=1", NULL}, 0},
1250 1251

    {"exe \"four\"\"\"\"quotes next",
1252
     {"exe", "four\"quotes", "next", NULL}, 0},
1253 1254

    {"exe \"five\"\"\"\"\"quotes next",
1255
     {"exe", "five\"\"quotes", "next", NULL}, 0},
1256 1257

    {"exe \"six\"\"\"\"\"\" quotes\" next 7%3=1",
1258
     {"exe", "six\"\" quotes", "next", "7%3=1", NULL}, 0},
1259 1260

    {"exe \"eleven\"\"\"\"\"\"\"\"\"\"\"quotes next",
1261
     {"exe", "eleven\"\"\"\"quotes", "next", NULL}, 0},
1262 1263

    {"exe \"twelve\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1",
1264
     {"exe", "twelve\"\"\"\" quotes", "next", "13%3=1", NULL}, 0},
1265

1266 1267
    /* Escaped consecutive quotes are fun */
    {"exe \"the crazy \\\\\"\"\"\\\\\" quotes",
1268
     {"exe", "the crazy \\\"\\", "quotes", NULL}, 0},
1269

1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281
    /* The executable path has its own rules!!!
     * - Backslashes have no special meaning.
     * - If the first character is a quote, then the second quote ends the
     *   executable path.
     * - The previous rule holds even if the next character is not a space!
     * - If the first character is not a quote, then quotes have no special
     *   meaning either and the executable path stops at the first space.
     * - The consecutive quotes rules don't apply either.
     * - Even if there is no space between the executable path and the first
     *   argument, the latter is parsed using the regular rules.
     */
    {"exe\"file\"path arg1",
1282
     {"exe\"file\"path", "arg1", NULL}, 0},
1283 1284

    {"exe\"file\"path\targ1",
1285
     {"exe\"file\"path", "arg1", NULL}, 0},
1286 1287

    {"exe\"path\\ arg1",
1288
     {"exe\"path\\", "arg1", NULL}, 0},
1289 1290

    {"\\\"exe \"arg one\"",
1291
     {"\\\"exe", "arg one", NULL}, 0},
1292 1293 1294 1295

    {"\"spaced exe\" \"next arg\"",
     {"spaced exe", "next arg", NULL}, 0},

1296 1297 1298
    {"\"spaced exe\"\t\"next arg\"",
     {"spaced exe", "next arg", NULL}, 0},

1299
    {"\"exe\"arg\" one\" argtwo",
1300
     {"exe", "arg one", "argtwo", NULL}, 0},
1301 1302

    {"\"spaced exe\\\"arg1 arg2",
1303
     {"spaced exe\\", "arg1", "arg2", NULL}, 0},
1304 1305

    {"\"two\"\" arg1 ",
1306
     {"two", " arg1 ", NULL}, 0},
1307 1308

    {"\"three\"\"\" arg2",
1309
     {"three", "", "arg2", NULL}, 0},
1310 1311

    {"\"four\"\"\"\"arg1",
1312
     {"four", "\"arg1", NULL}, 0},
1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336

    /* If the first character is a space then the executable path is empty */
    {" \"arg\"one argtwo",
     {"", "argone", "argtwo", NULL}, 0},

    {NULL, {NULL}, 0}
};

static BOOL test_one_cmdline(const cmdline_tests_t* test)
{
    WCHAR cmdW[MAX_PATH], argW[MAX_PATH];
    LPWSTR *cl2a;
    int cl2a_count;
    LPWSTR *argsW;
    int i, count;

    /* trace("----- cmd='%s'\n", test->cmd); */
    MultiByteToWideChar(CP_ACP, 0, test->cmd, -1, cmdW, sizeof(cmdW)/sizeof(*cmdW));
    argsW = cl2a = CommandLineToArgvW(cmdW, &cl2a_count);
    if (argsW == NULL && cl2a_count == -1)
    {
        win_skip("CommandLineToArgvW not implemented, skipping\n");
        return FALSE;
    }
1337 1338
    ok(!argsW[cl2a_count] || broken(argsW[cl2a_count] != NULL) /* before Vista */,
       "expected NULL-terminated list of commandline arguments\n");
1339 1340 1341 1342

    count = 0;
    while (test->args[count])
        count++;
1343
    todo_wine_if(test->todo & 0x1)
1344 1345
        ok(cl2a_count == count, "%s: expected %d arguments, but got %d\n", test->cmd, count, cl2a_count);

1346
    for (i = 0; i < cl2a_count; i++)
1347
    {
1348
        if (i < count)
1349 1350
        {
            MultiByteToWideChar(CP_ACP, 0, test->args[i], -1, argW, sizeof(argW)/sizeof(*argW));
1351
            todo_wine_if(test->todo & (1 << (i+4)))
1352 1353
                ok(!lstrcmpW(*argsW, argW), "%s: arg[%d] expected %s but got %s\n", test->cmd, i, wine_dbgstr_w(argW), wine_dbgstr_w(*argsW));
        }
1354
        else todo_wine_if(test->todo & 0x1)
1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390
            ok(0, "%s: got extra arg[%d]=%s\n", test->cmd, i, wine_dbgstr_w(*argsW));
        argsW++;
    }
    LocalFree(cl2a);
    return TRUE;
}

static void test_commandline2argv(void)
{
    static const WCHAR exeW[] = {'e','x','e',0};
    const cmdline_tests_t* test;
    WCHAR strW[MAX_PATH];
    LPWSTR *args;
    int numargs;
    DWORD le;

    test = cmdline_tests;
    while (test->cmd)
    {
        if (!test_one_cmdline(test))
            return;
        test++;
    }

    SetLastError(0xdeadbeef);
    args = CommandLineToArgvW(exeW, NULL);
    le = GetLastError();
    ok(args == NULL && le == ERROR_INVALID_PARAMETER, "expected NULL with ERROR_INVALID_PARAMETER got %p with %u\n", args, le);

    SetLastError(0xdeadbeef);
    args = CommandLineToArgvW(NULL, NULL);
    le = GetLastError();
    ok(args == NULL && le == ERROR_INVALID_PARAMETER, "expected NULL with ERROR_INVALID_PARAMETER got %p with %u\n", args, le);

    *strW = 0;
    args = CommandLineToArgvW(strW, &numargs);
1391
    ok(numargs == 1 || broken(numargs > 1), "expected 1 args, got %d\n", numargs);
1392 1393
    ok(!args || (!args[numargs] || broken(args[numargs] != NULL) /* before Vista */),
       "expected NULL-terminated list of commandline arguments\n");
1394 1395 1396 1397 1398 1399 1400 1401
    if (numargs == 1)
    {
        GetModuleFileNameW(NULL, strW, sizeof(strW)/sizeof(*strW));
        ok(!lstrcmpW(args[0], strW), "wrong path to the current executable: %s instead of %s\n", wine_dbgstr_w(args[0]), wine_dbgstr_w(strW));
    }
    if (args) LocalFree(args);
}

1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414
/* The goal here is to analyze how ShellExecute() builds the command that
 * will be run. The tricky part is that there are three transformation
 * steps between the 'parameters' string we pass to ShellExecute() and the
 * argument list we observe in the child process:
 * - The parsing of 'parameters' string into individual arguments. The tests
 *   show this is done differently from both CreateProcess() and
 *   CommandLineToArgv()!
 * - The way the command 'formatting directives' such as %1, %2, etc are
 *   handled.
 * - And the way the resulting command line is then parsed to yield the
 *   argument list we check.
 */
typedef struct
1415
{
1416 1417 1418 1419
    const char* verb;
    const char* params;
    int todo;
    cmdline_tests_t cmd;
1420
    cmdline_tests_t broken;
1421
} argify_tests_t;
1422

1423 1424 1425 1426 1427 1428 1429 1430 1431 1432
static const argify_tests_t argify_tests[] =
{
    /* Start with three simple parameters. Notice that one can reorder and
     * duplicate the parameters. Also notice how %* take the raw input
     * parameters string, including the trailing spaces, no matter what
     * arguments have already been used.
     */
    {"Params232S", "p2 p3 p4 ", 0xc2,
     {" p2 p3 \"p2\" \"p2 p3 p4 \"",
      {"", "p2", "p3", "p2", "p2 p3 p4 ", NULL}, 0}},
1433

1434 1435 1436 1437 1438 1439 1440
    /* Unquoted argument references like %2 don't automatically quote their
     * argument. Similarly, when they are quoted they don't escape the quotes
     * that their argument may contain.
     */
    {"Params232S", "\"p two\" p3 p4  ", 0x3f3,
     {" p two p3 \"p two\" \"\"p two\" p3 p4  \"",
      {"", "p", "two", "p3", "p two", "p", "two p3 p4  ", NULL}, 0}},
1441

1442 1443 1444 1445 1446 1447 1448 1449 1450 1451
    /* Only single digits are supported so only %1 to %9. Shown here with %20
     * because %10 is a pain.
     */
    {"Params20", "p", 0,
     {" \"p0\"",
      {"", "p0", NULL}, 0}},

    /* Only (double-)quotes have a special meaning. */
    {"Params23456", "'p2 p3` p4\\ $even", 0x40,
     {" \"'p2\" \"p3`\" \"p4\\\" \"$even\" \"\"",
1452
      {"", "'p2", "p3`", "p4\" $even \"", NULL}, 0}},
1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466

    {"Params23456", "p=2 p-3 p4\tp4\rp4\np4", 0x1c2,
     {" \"p=2\" \"p-3\" \"p4\tp4\rp4\np4\" \"\" \"\"",
      {"", "p=2", "p-3", "p4\tp4\rp4\np4", "", "", NULL}, 0}},

    /* In unquoted strings, quotes are treated are a parameter separator just
     * like spaces! However they can be doubled to get a literal quote.
     * Specifically:
     * 2n   quotes -> n quotes
     * 2n+1 quotes -> n quotes and a parameter separator
     */
    {"Params23456789", "one\"quote \"p four\" one\"quote p7", 0xff3,
     {" \"one\" \"quote\" \"p four\" \"one\" \"quote\" \"p7\" \"\" \"\"",
      {"", "one", "quote", "p four", "one", "quote", "p7", "", "", NULL}, 0}},
1467

1468 1469 1470
    {"Params23456789", "two\"\"quotes \"p three\" two\"\"quotes p5", 0xf2,
     {" \"two\"quotes\" \"p three\" \"two\"quotes\" \"p5\" \"\" \"\" \"\" \"\"",
      {"", "twoquotes p", "three twoquotes", "p5", "", "", "", "", NULL}, 0}},
1471

1472 1473
    {"Params23456789", "three\"\"\"quotes \"p four\" three\"\"\"quotes p6", 0xff3,
     {" \"three\"\" \"quotes\" \"p four\" \"three\"\" \"quotes\" \"p6\" \"\" \"\"",
1474
      {"", "three\"", "quotes", "p four", "three\"", "quotes", "p6", "", "", NULL}, 0}},
1475

1476 1477
    {"Params23456789", "four\"\"\"\"quotes \"p three\" four\"\"\"\"quotes p5", 0xf3,
     {" \"four\"\"quotes\" \"p three\" \"four\"\"quotes\" \"p5\" \"\" \"\" \"\" \"\"",
1478
      {"", "four\"quotes p", "three fourquotes p5 \"", "", "", "", NULL}, 0}},
1479

1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494
    /* Quoted strings cannot be continued by tacking on a non space character
     * either.
     */
    {"Params23456", "\"p two\"p3 \"p four\"p5 p6", 0x1f3,
     {" \"p two\" \"p3\" \"p four\" \"p5\" \"p6\"",
      {"", "p two", "p3", "p four", "p5", "p6", NULL}, 0}},

    /* In quoted strings, the quotes are halved and an odd number closes the
     * string. Specifically:
     * 2n   quotes -> n quotes
     * 2n+1 quotes -> n quotes and closes the string and hence the parameter
     */
    {"Params23456789", "\"one q\"uote \"p four\" \"one q\"uote p7", 0xff3,
     {" \"one q\" \"uote\" \"p four\" \"one q\" \"uote\" \"p7\" \"\" \"\"",
      {"", "one q", "uote", "p four", "one q", "uote", "p7", "", "", NULL}, 0}},
1495

1496 1497 1498
    {"Params23456789", "\"two \"\" quotes\" \"p three\" \"two \"\" quotes\" p5", 0x1ff3,
     {" \"two \" quotes\" \"p three\" \"two \" quotes\" \"p5\" \"\" \"\" \"\" \"\"",
      {"", "two ", "quotes p", "three two", " quotes", "p5", "", "", "", "", NULL}, 0}},
1499

1500 1501
    {"Params23456789", "\"three q\"\"\"uotes \"p four\" \"three q\"\"\"uotes p7", 0xff3,
     {" \"three q\"\" \"uotes\" \"p four\" \"three q\"\" \"uotes\" \"p7\" \"\" \"\"",
1502
      {"", "three q\"", "uotes", "p four", "three q\"", "uotes", "p7", "", "", NULL}, 0}},
1503 1504 1505

    {"Params23456789", "\"four \"\"\"\" quotes\" \"p three\" \"four \"\"\"\" quotes\" p5", 0xff3,
     {" \"four \"\" quotes\" \"p three\" \"four \"\" quotes\" \"p5\" \"\" \"\" \"\" \"\"",
1506
      {"", "four \"", "quotes p", "three four", "", "quotes p5 \"", "", "", "", NULL}, 0}},
1507 1508 1509 1510 1511 1512 1513 1514 1515 1516

    /* The quoted string rules also apply to consecutive quotes at the start
     * of a parameter but don't count the opening quote!
     */
    {"Params23456789", "\"\"twoquotes \"p four\" \"\"twoquotes p7", 0xbf3,
     {" \"\" \"twoquotes\" \"p four\" \"\" \"twoquotes\" \"p7\" \"\" \"\"",
      {"", "", "twoquotes", "p four", "", "twoquotes", "p7", "", "", NULL}, 0}},

    {"Params23456789", "\"\"\"three quotes\" \"p three\" \"\"\"three quotes\" p5", 0x6f3,
     {" \"\"three quotes\" \"p three\" \"\"three quotes\" \"p5\" \"\" \"\" \"\" \"\"",
1517
      {"", "three", "quotes p", "three \"three", "quotes p5 \"", "", "", "", NULL}, 0}},
1518 1519 1520

    {"Params23456789", "\"\"\"\"fourquotes \"p four\" \"\"\"\"fourquotes p7", 0xbf3,
     {" \"\"\" \"fourquotes\" \"p four\" \"\"\" \"fourquotes\" \"p7\" \"\" \"\"",
1521
      {"", "\"", "fourquotes", "p four", "\"", "fourquotes", "p7", "", "", NULL}, 0}},
1522 1523 1524 1525

    /* An unclosed quoted string gets lost! */
    {"Params23456", "p2 \"p3\" \"p4 is lost", 0x1c3,
     {" \"p2\" \"p3\" \"\" \"\" \"\"",
1526 1527 1528
      {"", "p2", "p3", "", "", "", NULL}, 0},
     {" \"p2\" \"p3\" \"p3\" \"\" \"\"",
       {"", "p2", "p3", "p3", "", "", NULL}, 0}},
1529 1530 1531 1532 1533 1534 1535 1536 1537

    /* Backslashes have no special meaning even when preceding quotes. All
     * they do is start an unquoted string.
     */
    {"Params23456", "\\\"p\\three \"pfour\\\" pfive", 0x73,
     {" \"\\\" \"p\\three\" \"pfour\\\" \"pfive\" \"\"",
      {"", "\" p\\three pfour\"", "pfive", "", NULL}, 0}},

    /* Environment variables are left untouched. */
1538
    {"Params23456", "%TMPDIR% %t %c", 0,
1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579
     {" \"%TMPDIR%\" \"%t\" \"%c\" \"\" \"\"",
      {"", "%TMPDIR%", "%t", "%c", "", "", NULL}, 0}},

    /* %~2 is equivalent to %*. However %~3 and higher include the spaces
     * before the parameter!
     * (but not the previous parameter's closing quote fortunately)
     */
    {"Params2345Etc", "p2  p3 \"p4\"  p5 p6 ", 0x3f3,
     {" ~2=\"p2  p3 \"p4\"  p5 p6 \" ~3=\"  p3 \"p4\"  p5 p6 \" ~4=\" \"p4\"  p5 p6 \" ~5=  p5 p6 ",
      {"", "~2=p2  p3 p4  p5 p6 ", "~3=  p3 p4  p5 p6 ", "~4= p4  p5 p6 ", "~5=", "p5", "p6", NULL}, 0}},

    /* %~n works even if there is no nth parameter. */
    {"Params9Etc", "p2 p3 p4 p5 p6 p7 p8   ", 0x12,
     {" ~9=\"   \"",
      {"", "~9=   ", NULL}, 0}},

    {"Params9Etc", "p2 p3 p4 p5 p6 p7   ", 0x12,
     {" ~9=\"\"",
      {"", "~9=", NULL}, 0}},

    /* The %~n directives also transmit the tenth parameter and beyond. */
    {"Params9Etc", "p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 and beyond!", 0x12,
     {" ~9=\" p9 p10 p11 and beyond!\"",
      {"", "~9= p9 p10 p11 and beyond!", NULL}, 0}},

    /* Bad formatting directives lose their % sign, except those followed by
     * a tilde! Environment variables are not expanded but lose their % sign.
     */
    {"ParamsBad", "p2 p3 p4 p5", 0x12,
     {" \"% - %~ %~0 %~1 %~a %~* a b c TMPDIR\"",
      {"", "% - %~ %~0 %~1 %~a %~* a b c TMPDIR", NULL}, 0}},

    {NULL, NULL, 0, {NULL, {NULL}, 0}}
};

static void test_argify(void)
{
    BOOL has_cl2a = TRUE;
    char fileA[MAX_PATH], params[2*MAX_PATH+12];
    INT_PTR rc;
    const argify_tests_t* test;
1580
    const cmdline_tests_t *bad;
1581 1582 1583
    const char* cmd;
    unsigned i, count;

1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595
    /* Test with a long parameter */
    for (rc = 0; rc < MAX_PATH; rc++)
        fileA[rc] = 'a' + rc % 26;
    fileA[MAX_PATH-1] = '\0';
    sprintf(params, "shlexec \"%s\" %s", child_file, fileA);

    /* We need NOZONECHECKS on Win2003 to block a dialog */
    rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params, NULL, NULL);
    okShell(rc > 32, "failed: rc=%lu\n", rc);
    okChildInt("argcA", 4);
    okChildPath("argvA3", fileA);

1596 1597 1598 1599 1600 1601
    if (skip_shlexec_tests)
    {
        skip("No argify tests due to lack of .shlexec association\n");
        return;
    }

1602 1603 1604 1605 1606 1607 1608
    create_test_verb("shlexec.shlexec", "Params232S", 0, "Params232S %2 %3 \"%2\" \"%*\"");
    create_test_verb("shlexec.shlexec", "Params23456", 0, "Params23456 \"%2\" \"%3\" \"%4\" \"%5\" \"%6\"");
    create_test_verb("shlexec.shlexec", "Params23456789", 0, "Params23456789 \"%2\" \"%3\" \"%4\" \"%5\" \"%6\" \"%7\" \"%8\" \"%9\"");
    create_test_verb("shlexec.shlexec", "Params2345Etc", 0, "Params2345Etc ~2=\"%~2\" ~3=\"%~3\" ~4=\"%~4\" ~5=%~5");
    create_test_verb("shlexec.shlexec", "Params9Etc", 0, "Params9Etc ~9=\"%~9\"");
    create_test_verb("shlexec.shlexec", "Params20", 0, "Params20 \"%20\"");
    create_test_verb("shlexec.shlexec", "ParamsBad", 0, "ParamsBad \"%% %- %~ %~0 %~1 %~a %~* %a %b %c %TMPDIR%\"");
1609 1610 1611 1612 1613

    sprintf(fileA, "%s\\test file.shlexec", tmpdir);

    test = argify_tests;
    while (test->params)
1614
    {
1615 1616
        bad = test->broken.cmd ? &test->broken : &test->cmd;

1617 1618
        /* trace("***** verb='%s' params='%s'\n", test->verb, test->params); */
        rc = shell_execute_ex(SEE_MASK_DOENVSUBST, test->verb, fileA, test->params, NULL, NULL);
1619
        okShell(rc > 32, "failed: rc=%lu\n", rc);
1620 1621 1622 1623

        count = 0;
        while (test->cmd.args[count])
            count++;
1624 1625 1626 1627
        /* +4 for the shlexec arguments, -1 because of the added ""
         * argument for the CommandLineToArgvW() tests.
         */
        todo_wine_if(test->todo & 0x1)
1628 1629
            okChildInt("argcA", 4 + count - 1);

1630
        cmd = getChildString("Child", "cmdlineA");
1631 1632 1633 1634 1635 1636
        /* Our commands are such that the verb immediately precedes the
         * part we are interested in.
         */
        if (cmd) cmd = strstr(cmd, test->verb);
        if (cmd) cmd += strlen(test->verb);
        if (!cmd) cmd = "(null)";
1637
        todo_wine_if(test->todo & 0x2)
1638 1639
            okShell(!strcmp(cmd, test->cmd.cmd) || broken(!strcmp(cmd, bad->cmd)),
                    "the cmdline is '%s' instead of '%s'\n", cmd, test->cmd.cmd);
1640 1641 1642 1643 1644

        for (i = 0; i < count - 1; i++)
        {
            char argname[18];
            sprintf(argname, "argvA%d", 4 + i);
1645
            todo_wine_if(test->todo & (1 << (i+4)))
1646
                okChildStringBroken(argname, test->cmd.args[i+1], bad->args[i+1]);
1647
        }
1648 1649 1650 1651

        if (has_cl2a)
            has_cl2a = test_one_cmdline(&(test->cmd));
        test++;
1652
    }
1653 1654
}

1655
static void test_filename(void)
1656 1657 1658 1659
{
    char filename[MAX_PATH];
    const filename_tests_t* test;
    char* c;
1660
    INT_PTR rc;
1661

1662 1663 1664 1665 1666 1667
    if (skip_shlexec_tests)
    {
        skip("No ShellExecute/filename tests due to lack of .shlexec association\n");
        return;
    }

1668 1669 1670
    test=filename_tests;
    while (test->basename)
    {
1671 1672
        BOOL quotedfile = FALSE;

1673 1674 1675 1676 1677 1678 1679
        if (skip_noassoc_tests && test->rc == SE_ERR_NOASSOC)
        {
            win_skip("Skipping shellexecute of file with unassociated extension\n");
            test++;
            continue;
        }

1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690
        sprintf(filename, test->basename, tmpdir);
        if (strchr(filename, '/'))
        {
            c=filename;
            while (*c)
            {
                if (*c=='\\')
                    *c='/';
                c++;
            }
        }
1691 1692 1693 1694 1695 1696 1697
        if ((test->todo & 0x40)==0)
        {
            rc=shell_execute(test->verb, filename, NULL, NULL);
        }
        else
        {
            char quoted[MAX_PATH + 2];
1698 1699

            quotedfile = TRUE;
1700 1701 1702
            sprintf(quoted, "\"%s\"", filename);
            rc=shell_execute(test->verb, quoted, NULL, NULL);
        }
1703 1704
        if (rc > 32)
            rc=33;
1705 1706 1707
        okShell(rc==test->rc ||
                broken(quotedfile && rc == SE_ERR_FNF), /* NT4 */
                "failed: rc=%ld err=%u\n", rc, GetLastError());
1708
        if (rc == 33)
1709
        {
1710
            const char* verb;
1711
            todo_wine_if(test->todo & 0x2)
1712
                okChildInt("argcA", 5);
1713
            verb=(test->verb ? test->verb : "Open");
1714
            todo_wine_if(test->todo & 0x4)
1715
                okChildString("argvA3", verb);
1716
            todo_wine_if(test->todo & 0x8)
1717
                okChildPath("argvA4", filename);
1718 1719 1720 1721
        }
        test++;
    }

1722 1723 1724 1725 1726
    test=noquotes_tests;
    while (test->basename)
    {
        sprintf(filename, test->basename, tmpdir);
        rc=shell_execute(test->verb, filename, NULL, NULL);
1727 1728
        if (rc > 32)
            rc=33;
1729
        todo_wine_if(test->todo & 0x1)
1730
            okShell(rc==test->rc, "failed: rc=%ld err=%u\n", rc, GetLastError());
1731 1732 1733 1734 1735 1736 1737
        if (rc==0)
        {
            int count;
            const char* verb;
            char* str;

            verb=(test->verb ? test->verb : "Open");
1738
            todo_wine_if(test->todo & 0x4)
1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750
                okChildString("argvA3", verb);

            count=4;
            str=filename;
            while (1)
            {
                char attrib[18];
                char* space;
                space=strchr(str, ' ');
                if (space)
                    *space='\0';
                sprintf(attrib, "argvA%d", count);
1751
                todo_wine_if(test->todo & 0x8)
1752 1753 1754 1755 1756 1757
                    okChildPath(attrib, str);
                count++;
                if (!space)
                    break;
                str=space+1;
            }
1758
            todo_wine_if(test->todo & 0x2)
1759 1760 1761 1762 1763
                okChildInt("argcA", count);
        }
        test++;
    }

1764
    if (dllver.dwMajorVersion != 0)
1765 1766 1767 1768 1769 1770 1771 1772 1773
    {
        /* The more recent versions of shell32.dll accept quoted filenames
         * while older ones (e.g. 4.00) don't. Still we want to test this
         * because IE 6 depends on the new behavior.
         * One day we may need to check the exact version of the dll but for
         * now making sure DllGetVersion() is present is sufficient.
         */
        sprintf(filename, "\"%s\\test file.shlexec\"", tmpdir);
        rc=shell_execute(NULL, filename, NULL, NULL);
1774
        okShell(rc > 32, "failed: rc=%ld err=%u\n", rc, GetLastError());
1775 1776 1777 1778 1779
        okChildInt("argcA", 5);
        okChildString("argvA3", "Open");
        sprintf(filename, "%s\\test file.shlexec", tmpdir);
        okChildPath("argvA4", filename);
    }
1780 1781 1782 1783 1784 1785 1786 1787

    sprintf(filename, "\"%s\\test file.sha\"", tmpdir);
    rc=shell_execute(NULL, filename, NULL, NULL);
    todo_wine okShell(rc > 32, "failed: rc=%ld err=%u\n", rc, GetLastError());
    okChildInt("argcA", 5);
    todo_wine okChildString("argvA3", "averb");
    sprintf(filename, "%s\\test file.sha", tmpdir);
    todo_wine okChildPath("argvA4", filename);
1788 1789
}

1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803
typedef struct
{
    const char* urlprefix;
    const char* basename;
    int flags;
    int todo;
} fileurl_tests_t;

#define URL_SUCCESS  0x1
#define USE_COLON    0x2
#define USE_BSLASH   0x4

static fileurl_tests_t fileurl_tests[]=
{
1804
    /* How many slashes does it take... */
1805 1806 1807 1808 1809 1810 1811
    {"file:", "%s\\test file.shlexec", URL_SUCCESS, 0},
    {"file:/", "%s\\test file.shlexec", URL_SUCCESS, 0},
    {"file://", "%s\\test file.shlexec", URL_SUCCESS, 0},
    {"file:///", "%s\\test file.shlexec", URL_SUCCESS, 0},
    {"File:///", "%s\\test file.shlexec", URL_SUCCESS, 0},
    {"file:////", "%s\\test file.shlexec", URL_SUCCESS, 0},
    {"file://///", "%s\\test file.shlexec", 0, 0},
1812 1813

    /* Test with Windows-style paths */
1814 1815
    {"file:///", "%s\\test file.shlexec", URL_SUCCESS | USE_COLON, 0},
    {"file:///", "%s\\test file.shlexec", URL_SUCCESS | USE_BSLASH, 0},
1816 1817

    /* Check handling of hostnames */
1818 1819 1820 1821 1822 1823
    {"file://localhost/", "%s\\test file.shlexec", URL_SUCCESS, 0},
    {"file://localhost:80/", "%s\\test file.shlexec", 0, 0},
    {"file://LocalHost/", "%s\\test file.shlexec", URL_SUCCESS, 0},
    {"file://127.0.0.1/", "%s\\test file.shlexec", 0, 0},
    {"file://::1/", "%s\\test file.shlexec", 0, 0},
    {"file://notahost/", "%s\\test file.shlexec", 0, 0},
1824 1825 1826

    /* Environment variables are not expanded in URLs */
    {"%urlprefix%", "%s\\test file.shlexec", 0, 0x1},
1827 1828 1829 1830
    {"file:///", "%%TMPDIR%%\\test file.shlexec", 0, 0},

    /* Test shortcuts vs. URLs */
    {"file://///", "%s\\test_shortcut_shlexec.lnk", 0, 0x1d},
1831

1832 1833 1834
    /* Confuse things by mixing protocols */
    {"file://", "shlproto://foo/bar", USE_COLON, 0},

1835 1836 1837
    {NULL, NULL, 0, 0}
};

1838
static void test_fileurls(void)
1839 1840 1841 1842 1843 1844 1845
{
    char filename[MAX_PATH], fileurl[MAX_PATH], longtmpdir[MAX_PATH];
    char command[MAX_PATH];
    const fileurl_tests_t* test;
    char *s;
    INT_PTR rc;

1846 1847 1848 1849 1850 1851
    if (skip_shlexec_tests)
    {
        skip("No file URL tests due to lack of .shlexec association\n");
        return;
    }

1852 1853
    rc = shell_execute_ex(SEE_MASK_FLAG_NO_UI, NULL,
                          "file:///nosuchfile.shlexec", NULL, NULL, NULL);
1854 1855 1856 1857 1858 1859 1860
    if (rc > 32)
    {
        win_skip("shell32 is too old (likely < 4.72). Skipping the file URL tests\n");
        return;
    }

    get_long_path_name(tmpdir, longtmpdir, sizeof(longtmpdir)/sizeof(*longtmpdir));
1861
    SetEnvironmentVariableA("urlprefix", "file:///");
1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884

    test=fileurl_tests;
    while (test->basename)
    {
        /* Build the file URL */
        sprintf(filename, test->basename, longtmpdir);
        strcpy(fileurl, test->urlprefix);
        strcat(fileurl, filename);
        s = fileurl + strlen(test->urlprefix);
        while (*s)
        {
            if (!(test->flags & USE_COLON) && *s == ':')
                *s = '|';
            else if (!(test->flags & USE_BSLASH) && *s == '\\')
                *s = '/';
            s++;
        }

        /* Test it first with FindExecutable() */
        rc = (INT_PTR)FindExecutableA(fileurl, NULL, command);
        ok(rc == SE_ERR_FNF, "FindExecutable(%s) failed: bad rc=%lu\n", fileurl, rc);

        /* Then ShellExecute() */
1885 1886 1887 1888
        if ((test->todo & 0x10) == 0)
            rc = shell_execute(NULL, fileurl, NULL, NULL);
        else todo_wait
            rc = shell_execute(NULL, fileurl, NULL, NULL);
1889 1890 1891 1892 1893 1894 1895
        if (bad_shellexecute)
        {
            win_skip("shell32 is too old (likely 4.72). Skipping the file URL tests\n");
            break;
        }
        if (test->flags & URL_SUCCESS)
        {
1896
            todo_wine_if(test->todo & 0x1)
1897
                okShell(rc > 32, "failed: bad rc=%lu\n", rc);
1898 1899 1900
        }
        else
        {
1901
            todo_wine_if(test->todo & 0x1)
1902 1903 1904
                okShell(rc == SE_ERR_FNF || rc == SE_ERR_PNF ||
                        broken(rc == SE_ERR_ACCESSDENIED) /* win2000 */,
                        "failed: bad rc=%lu\n", rc);
1905 1906 1907
        }
        if (rc == 33)
        {
1908
            todo_wine_if(test->todo & 0x2)
1909
                okChildInt("argcA", 5);
1910
            todo_wine_if(test->todo & 0x4)
1911
                okChildString("argvA3", "Open");
1912
            todo_wine_if(test->todo & 0x8)
1913 1914 1915 1916
                okChildPath("argvA4", filename);
        }
        test++;
    }
1917

1918
    SetEnvironmentVariableA("urlprefix", NULL);
1919 1920
}

1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953
static void test_urls(void)
{
    char url[MAX_PATH];
    INT_PTR rc;

    if (!create_test_class("fakeproto", FALSE))
    {
        skip("Unable to create 'fakeproto' class for URL tests\n");
        return;
    }
    create_test_verb("fakeproto", "open", 0, "URL %1");

    create_test_class("shlpaverb", TRUE);
    create_test_verb("shlpaverb", "averb", 0, "PAVerb \"%1\"");

    /* Protocols must be properly declared */
    rc = shell_execute(NULL, "notaproto://foo", NULL, NULL);
    ok(rc == SE_ERR_NOASSOC || broken(rc == SE_ERR_ACCESSDENIED),
       "%s returned %lu\n", shell_call, rc);

    rc = shell_execute(NULL, "fakeproto://foo/bar", NULL, NULL);
    todo_wine ok(rc == SE_ERR_NOASSOC || broken(rc == SE_ERR_ACCESSDENIED),
                 "%s returned %lu\n", shell_call, rc);

    /* Here's a real live one */
    rc = shell_execute(NULL, "shlproto://foo/bar", NULL, NULL);
    ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
    okChildInt("argcA", 5);
    okChildString("argvA3", "URL");
    okChildString("argvA4", "shlproto://foo/bar");

    /* Check default verb detection */
    rc = shell_execute(NULL, "shlpaverb://foo/bar", NULL, NULL);
1954 1955 1956 1957 1958 1959 1960 1961 1962
    todo_wine ok(rc > 32 || /* XP+IE7 - Win10 */
                 broken(rc == SE_ERR_NOASSOC), /* XP+IE6 */
                 "%s failed: rc=%lu\n", shell_call, rc);
    if (rc > 32)
    {
        okChildInt("argcA", 5);
        todo_wine okChildString("argvA3", "PAVerb");
        todo_wine okChildString("argvA4", "shlpaverb://foo/bar");
    }
1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014

    /* But alternative verbs are a recent feature! */
    rc = shell_execute("averb", "shlproto://foo/bar", NULL, NULL);
    ok(rc > 32 || /* Win8 - Win10 */
       broken(rc == SE_ERR_ACCESSDENIED), /* XP - Win7 */
       "%s failed: rc=%lu\n", shell_call, rc);
    if (rc > 32)
    {
        okChildString("argvA3", "AVerb");
        okChildString("argvA4", "shlproto://foo/bar");
    }

    /* A .lnk ending does not turn a URL into a shortcut */
    todo_wait rc = shell_execute(NULL, "shlproto://foo/bar.lnk", NULL, NULL);
    ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
    okChildInt("argcA", 5);
    todo_wine okChildString("argvA3", "URL");
    todo_wine okChildString("argvA4", "shlproto://foo/bar.lnk");

    /* Neither does a .exe extension */
    rc = shell_execute(NULL, "shlproto://foo/bar.exe", NULL, NULL);
    ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
    okChildInt("argcA", 5);
    okChildString("argvA3", "URL");
    okChildString("argvA4", "shlproto://foo/bar.exe");

    /* But a class name overrides it */
    rc = shell_execute(NULL, "shlproto://foo/bar", "shlexec.shlexec", NULL);
    ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
    okChildInt("argcA", 5);
    okChildString("argvA3", "URL");
    okChildString("argvA4", "shlproto://foo/bar");

    /* Environment variables are expanded in URLs (but not in file URLs!) */
    rc = shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI,
                          NULL, "shlproto://%TMPDIR%/bar", NULL, NULL, NULL);
    okShell(rc > 32, "failed: rc=%lu\n", rc);
    okChildInt("argcA", 5);
    sprintf(url, "shlproto://%s/bar", tmpdir);
    okChildString("argvA3", "URL");
    okChildStringBroken("argvA4", url, "shlproto://%TMPDIR%/bar");

    /* But only after the path has been identified as a URL */
    SetEnvironmentVariableA("urlprefix", "shlproto:///");
    rc = shell_execute(NULL, "%urlprefix%foo", NULL, NULL);
    todo_wine ok(rc == SE_ERR_FNF, "%s returned %lu\n", shell_call, rc);
    SetEnvironmentVariableA("urlprefix", NULL);

    delete_test_class("fakeproto");
    delete_test_class("shlpaverb");
}

2015 2016
static void test_find_executable(void)
{
2017
    char notepad_path[MAX_PATH];
2018 2019 2020
    char filename[MAX_PATH];
    char command[MAX_PATH];
    const filename_tests_t* test;
2021
    INT_PTR rc;
2022

2023 2024 2025 2026 2027
    if (!create_test_association(".sfe"))
    {
        skip("Unable to create association for '.sfe'\n");
        return;
    }
2028
    create_test_verb("shlexec.sfe", "Open", 1, "%1");
2029 2030 2031 2032

    /* Don't test FindExecutable(..., NULL), it always crashes */

    strcpy(command, "your word");
2033 2034
    if (0) /* Can crash on Vista! */
    {
2035 2036
    rc=(INT_PTR)FindExecutableA(NULL, NULL, command);
    ok(rc == SE_ERR_FNF || rc > 32 /* nt4 */, "FindExecutable(NULL) returned %ld\n", rc);
2037
    ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command);
2038
    }
2039

2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054
    GetSystemDirectoryA( notepad_path, MAX_PATH );
    strcat( notepad_path, "\\notepad.exe" );

    /* Search for something that should be in the system-wide search path (no default directory) */
    strcpy(command, "your word");
    rc=(INT_PTR)FindExecutableA("notepad.exe", NULL, command);
    ok(rc > 32, "FindExecutable(%s) returned %ld\n", "notepad.exe", rc);
    ok(strcasecmp(command, notepad_path) == 0, "FindExecutable(%s) returned command=[%s]\n", "notepad.exe", command);

    /* Search for something that should be in the system-wide search path (with default directory) */
    strcpy(command, "your word");
    rc=(INT_PTR)FindExecutableA("notepad.exe", tmpdir, command);
    ok(rc > 32, "FindExecutable(%s) returned %ld\n", "notepad.exe", rc);
    ok(strcasecmp(command, notepad_path) == 0, "FindExecutable(%s) returned command=[%s]\n", "notepad.exe", command);

2055
    strcpy(command, "your word");
2056 2057
    rc=(INT_PTR)FindExecutableA(tmpdir, NULL, command);
    ok(rc == SE_ERR_NOASSOC /* >= win2000 */ || rc > 32 /* win98, nt4 */, "FindExecutable(NULL) returned %ld\n", rc);
2058 2059
    ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command);

2060
    sprintf(filename, "%s\\test file.sfe", tmpdir);
2061 2062
    rc=(INT_PTR)FindExecutableA(filename, NULL, command);
    ok(rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
2063 2064
    /* Depending on the platform, command could be '%1' or 'test file.sfe' */

2065 2066
    rc=(INT_PTR)FindExecutableA("test file.sfe", tmpdir, command);
    ok(rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
2067

2068 2069
    rc=(INT_PTR)FindExecutableA("test file.sfe", NULL, command);
    ok(rc == SE_ERR_FNF, "FindExecutable(%s) returned %ld\n", filename, rc);
2070 2071 2072

    delete_test_association(".sfe");

2073 2074 2075 2076 2077
    if (!create_test_association(".shl"))
    {
        skip("Unable to create association for '.shl'\n");
        return;
    }
2078
    create_test_verb("shlexec.shl", "Open", 0, "Open");
2079 2080

    sprintf(filename, "%s\\test file.shl", tmpdir);
2081 2082
    rc=(INT_PTR)FindExecutableA(filename, NULL, command);
    ok(rc == SE_ERR_FNF /* NT4 */ || rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
2083 2084

    sprintf(filename, "%s\\test file.shlfoo", tmpdir);
2085
    rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098

    delete_test_association(".shl");

    if (rc > 32)
    {
        /* On Windows XP and 2003 FindExecutable() is completely broken.
         * Probably what it does is convert the filename to 8.3 format,
         * which as a side effect converts the '.shlfoo' extension to '.shl',
         * and then tries to find an association for '.shl'. This means it
         * will normally fail on most extensions with more than 3 characters,
         * like '.mpeg', etc.
         * Also it means we cannot do any other test.
         */
2099
        win_skip("FindExecutable() is broken -> not running 4+ character extension tests\n");
2100 2101 2102
        return;
    }

2103 2104 2105 2106 2107 2108
    if (skip_shlexec_tests)
    {
        skip("No FindExecutable/filename tests due to lack of .shlexec association\n");
        return;
    }

2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123
    test=filename_tests;
    while (test->basename)
    {
        sprintf(filename, test->basename, tmpdir);
        if (strchr(filename, '/'))
        {
            char* c;
            c=filename;
            while (*c)
            {
                if (*c=='\\')
                    *c='/';
                c++;
            }
        }
2124 2125
        /* Win98 does not '\0'-terminate command! */
        memset(command, '\0', sizeof(command));
2126
        rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2127 2128
        if (rc > 32)
            rc=33;
2129
        todo_wine_if(test->todo & 0x10)
2130
            ok(rc==test->rc, "FindExecutable(%s) failed: rc=%ld\n", filename, rc);
2131 2132
        if (rc > 32)
        {
2133
            BOOL equal;
2134 2135 2136
            equal=strcmp(command, argv0) == 0 ||
                /* NT4 returns an extra 0x8 character! */
                (strlen(command) == strlen(argv0)+1 && strncmp(command, argv0, strlen(argv0)) == 0);
2137
            todo_wine_if(test->todo & 0x20)
2138
                ok(equal, "FindExecutable(%s) returned command='%s' instead of '%s'\n",
2139 2140 2141 2142 2143 2144
                   filename, command, argv0);
        }
        test++;
    }
}

2145 2146 2147 2148

static filename_tests_t lnk_tests[]=
{
    /* Pass bad / nonexistent filenames as a parameter */
2149 2150
    {NULL, "%s\\nonexistent.shlexec",    0xa, 33},
    {NULL, "%s\\nonexistent.noassoc",    0xa, 33},
2151 2152

    /* Pass regular paths as a parameter */
2153 2154
    {NULL, "%s\\test file.shlexec",      0xa, 33},
    {NULL, "%s/%%nasty%% $file.shlexec", 0xa, 33},
2155

2156
    /* Pass filenames with no association as a parameter */
2157
    {NULL, "%s\\test file.noassoc",      0xa, 33},
2158

2159
    {NULL, NULL, 0}
2160 2161
};

2162
static void test_lnks(void)
2163 2164 2165 2166
{
    char filename[MAX_PATH];
    char params[MAX_PATH];
    const filename_tests_t* test;
2167
    INT_PTR rc;
2168

2169 2170 2171 2172 2173 2174 2175
    if (skip_shlexec_tests)
        skip("No FindExecutable/filename tests due to lack of .shlexec association\n");
    else
    {
        /* Should open through our association */
        sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
        rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL);
2176
        okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2177 2178 2179 2180 2181
        okChildInt("argcA", 5);
        okChildString("argvA3", "Open");
        sprintf(params, "%s\\test file.shlexec", tmpdir);
        get_long_path_name(params, filename, sizeof(filename));
        okChildPath("argvA4", filename);
2182

2183
        todo_wait rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_DOENVSUBST, NULL, "%TMPDIR%\\test_shortcut_shlexec.lnk", NULL, NULL, NULL);
2184
        okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2185 2186 2187 2188 2189 2190
        okChildInt("argcA", 5);
        todo_wine okChildString("argvA3", "Open");
        sprintf(params, "%s\\test file.shlexec", tmpdir);
        get_long_path_name(params, filename, sizeof(filename));
        todo_wine okChildPath("argvA4", filename);
    }
2191 2192

    /* Should just run our executable */
2193
    sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2194
    rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL);
2195
    okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2196 2197 2198
    okChildInt("argcA", 4);
    okChildString("argvA3", "Lnk");

2199 2200 2201 2202 2203 2204 2205 2206 2207
    if (!skip_shlexec_tests)
    {
        /* An explicit class overrides lnk's ContextMenuHandler */
        rc=shell_execute_ex(SEE_MASK_CLASSNAME | SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, "shlexec.shlexec");
        okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
        okChildInt("argcA", 5);
        okChildString("argvA3", "Open");
        okChildPath("argvA4", filename);
    }
2208 2209 2210 2211 2212 2213 2214 2215 2216 2217

    if (dllver.dwMajorVersion>=6)
    {
        char* c;
       /* Recent versions of shell32.dll accept '/'s in shortcut paths.
         * Older versions don't or are quite buggy in this regard.
         */
        sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
        c=filename;
        while (*c)
2218
        {
2219 2220 2221 2222
            if (*c=='\\')
                *c='/';
            c++;
        }
2223
        rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL);
2224
        okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236
        okChildInt("argcA", 4);
        okChildString("argvA3", "Lnk");
    }

    sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
    test=lnk_tests;
    while (test->basename)
    {
        params[0]='\"';
        sprintf(params+1, test->basename, tmpdir);
        strcat(params,"\"");
        rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, params,
2237
                            NULL, NULL);
2238 2239
        if (rc > 32)
            rc=33;
2240
        todo_wine_if(test->todo & 0x1)
2241
            okShell(rc==test->rc, "failed: rc=%lu err=%u\n", rc, GetLastError());
2242 2243
        if (rc==0)
        {
2244
            todo_wine_if(test->todo & 0x2)
2245
                okChildInt("argcA", 5);
2246
            todo_wine_if(test->todo & 0x4)
2247 2248
                okChildString("argvA3", "Lnk");
            sprintf(params, test->basename, tmpdir);
2249
            okChildPath("argvA4", params);
2250
        }
2251
        test++;
2252 2253 2254 2255
    }
}


2256
static void test_exes(void)
2257 2258
{
    char filename[MAX_PATH];
2259
    char params[1024];
2260
    INT_PTR rc;
2261

2262 2263
    sprintf(params, "shlexec \"%s\" Exec", child_file);

2264
    /* We need NOZONECHECKS on Win2003 to block a dialog */
2265
    rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params,
2266
                        NULL, NULL);
2267
    okShell(rc > 32, "returned %lu\n", rc);
2268 2269
    okChildInt("argcA", 4);
    okChildString("argvA3", "Exec");
2270

2271
    if (! skip_noassoc_tests)
2272
    {
2273
        sprintf(filename, "%s\\test file.noassoc", tmpdir);
2274
        if (CopyFileA(argv0, filename, FALSE))
2275 2276 2277
        {
            rc=shell_execute(NULL, filename, params, NULL);
            todo_wine {
2278
                okShell(rc==SE_ERR_NOASSOC, "returned %lu\n", rc);
2279
            }
2280 2281
        }
    }
2282 2283 2284 2285
    else
    {
        win_skip("Skipping shellexecute of file with unassociated extension\n");
    }
2286 2287 2288 2289

    /* test combining executable and parameters */
    sprintf(filename, "%s shlexec \"%s\" Exec", argv0, child_file);
    rc = shell_execute(NULL, filename, NULL, NULL);
2290
    okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2291 2292 2293

    sprintf(filename, "\"%s\" shlexec \"%s\" Exec", argv0, child_file);
    rc = shell_execute(NULL, filename, NULL, NULL);
2294
    okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2295 2296 2297 2298 2299 2300

    /* A verb, even if invalid, overrides the normal handling of executables */
    todo_wait rc = shell_execute_ex(SEE_MASK_FLAG_NO_UI,
                                    "notaverb", argv0, NULL, NULL, NULL);
    todo_wine okShell(rc == SE_ERR_NOASSOC, "returned %lu\n", rc);

2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311
    if (!skip_shlexec_tests)
    {
        /* A class overrides the normal handling of executables too */
        /* FIXME SEE_MASK_FLAG_NO_UI is only needed due to Wine's bug */
        rc = shell_execute_ex(SEE_MASK_CLASSNAME | SEE_MASK_FLAG_NO_UI,
                              NULL, argv0, NULL, NULL, ".shlexec");
        todo_wine okShell(rc > 32, "returned %lu\n", rc);
        okChildInt("argcA", 5);
        todo_wine okChildString("argvA3", "Open");
        todo_wine okChildPath("argvA4", argv0);
    }
2312 2313
}

2314 2315 2316 2317 2318 2319 2320
typedef struct
{
    const char* command;
    const char* ddeexec;
    const char* application;
    const char* topic;
    const char* ifexec;
2321
    int expectedArgs;
2322
    const char* expectedDdeExec;
2323
    BOOL broken;
2324 2325 2326 2327 2328 2329
} dde_tests_t;

static dde_tests_t dde_tests[] =
{
    /* Test passing and not passing command-line
     * argument, no DDE */
2330 2331
    {"", NULL, NULL, NULL, NULL, 0, ""},
    {"\"%1\"", NULL, NULL, NULL, NULL, 1, ""},
2332 2333 2334

    /* Test passing and not passing command-line
     * argument, with DDE */
2335 2336
    {"", "[open(\"%1\")]", "shlexec", "dde", NULL, 0, "[open(\"%s\")]"},
    {"\"%1\"", "[open(\"%1\")]", "shlexec", "dde", NULL, 1, "[open(\"%s\")]"},
2337

2338 2339
    /* Test unquoted %1 in command and ddeexec
     * (test filename has space) */
2340
    {"%1", "[open(%1)]", "shlexec", "dde", NULL, 2, "[open(%s)]", TRUE /* before vista */},
2341

2342
    /* Test ifexec precedence over ddeexec */
2343
    {"", "[open(\"%1\")]", "shlexec", "dde", "[ifexec(\"%1\")]", 0, "[ifexec(\"%s\")]"},
2344 2345

    /* Test default DDE topic */
2346
    {"", "[open(\"%1\")]", "shlexec", NULL, NULL, 0, "[open(\"%s\")]"},
2347 2348

    /* Test default DDE application */
2349
    {"", "[open(\"%1\")]", NULL, "dde", NULL, 0, "[open(\"%s\")]"},
2350

2351
    {NULL}
2352 2353
};

2354
static int waitforinputidle_count;
2355
static DWORD WINAPI hooked_WaitForInputIdle(HANDLE process, DWORD timeout)
2356
{
2357
    waitforinputidle_count++;
2358
    if (winetest_debug > 1)
2359 2360
        trace("WaitForInputIdle() waiting for dde event timeout=min(%u,5s)\n", timeout);
    timeout = timeout < 5000 ? timeout : 5000;
2361
    return WaitForSingleObject(dde_ready_event, timeout);
2362 2363 2364 2365 2366 2367 2368
}

/*
 * WaitForInputIdle() will normally return immediately for console apps. That's
 * a problem for us because ShellExecute will assume that an app is ready to
 * receive DDE messages after it has called WaitForInputIdle() on that app.
 * To work around that we install our own version of WaitForInputIdle() that
2369 2370
 * will wait for the child to explicitly tell us that it is ready. We do that
 * by changing the entry for WaitForInputIdle() in the shell32 import address
2371 2372
 * table.
 */
2373
static void hook_WaitForInputIdle(DWORD (WINAPI *new_func)(HANDLE, DWORD))
2374 2375 2376 2377 2378
{
    char *base;
    PIMAGE_NT_HEADERS nt_headers;
    DWORD import_directory_rva;
    PIMAGE_IMPORT_DESCRIPTOR import_descriptor;
2379
    int hook_count = 0;
2380 2381 2382 2383 2384 2385 2386

    base = (char *) GetModuleHandleA("shell32.dll");
    nt_headers = (PIMAGE_NT_HEADERS)(base + ((PIMAGE_DOS_HEADER) base)->e_lfanew);
    import_directory_rva = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;

    /* Search for the correct imported module by walking the import descriptors */
    import_descriptor = (PIMAGE_IMPORT_DESCRIPTOR)(base + import_directory_rva);
2387
    while (U(*import_descriptor).OriginalFirstThunk != 0)
2388
    {
2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401
        char *import_module_name;

        import_module_name = base + import_descriptor->Name;
        if (lstrcmpiA(import_module_name, "user32.dll") == 0 ||
            lstrcmpiA(import_module_name, "user32") == 0)
        {
            PIMAGE_THUNK_DATA int_entry;
            PIMAGE_THUNK_DATA iat_entry;

            /* The import name table and import address table are two parallel
             * arrays. We need the import name table to find the imported
             * routine and the import address table to patch the address, so
             * walk them side by side */
2402
            int_entry = (PIMAGE_THUNK_DATA)(base + U(*import_descriptor).OriginalFirstThunk);
2403 2404
            iat_entry = (PIMAGE_THUNK_DATA)(base + import_descriptor->FirstThunk);
            while (int_entry->u1.Ordinal != 0)
2405
            {
2406
                if (! IMAGE_SNAP_BY_ORDINAL(int_entry->u1.Ordinal))
2407
                {
2408 2409 2410 2411 2412 2413 2414 2415 2416
                    PIMAGE_IMPORT_BY_NAME import_by_name;
                    import_by_name = (PIMAGE_IMPORT_BY_NAME)(base + int_entry->u1.AddressOfData);
                    if (lstrcmpA((char *) import_by_name->Name, "WaitForInputIdle") == 0)
                    {
                        /* Found the correct routine in the correct imported module. Patch it. */
                        DWORD old_prot;
                        VirtualProtect(&iat_entry->u1.Function, sizeof(ULONG_PTR), PAGE_READWRITE, &old_prot);
                        iat_entry->u1.Function = (ULONG_PTR) new_func;
                        VirtualProtect(&iat_entry->u1.Function, sizeof(ULONG_PTR), old_prot, &old_prot);
2417 2418 2419
                        if (winetest_debug > 1)
                            trace("Hooked %s.WaitForInputIdle\n", import_module_name);
                        hook_count++;
2420 2421
                        break;
                    }
2422
                }
2423 2424
                int_entry++;
                iat_entry++;
2425
            }
2426 2427
            break;
        }
2428

2429
        import_descriptor++;
2430
    }
2431
    ok(hook_count, "Could not hook WaitForInputIdle()\n");
2432 2433 2434 2435 2436 2437 2438
}

static void test_dde(void)
{
    char filename[MAX_PATH], defApplication[MAX_PATH];
    const dde_tests_t* test;
    char params[1024];
2439
    INT_PTR rc;
2440 2441
    HANDLE map;
    char *shared_block;
2442
    DWORD ddeflags;
2443

2444
    hook_WaitForInputIdle(hooked_WaitForInputIdle);
2445 2446 2447 2448 2449 2450 2451

    sprintf(filename, "%s\\test file.sde", tmpdir);

    /* Default service is application name minus path and extension */
    strcpy(defApplication, strrchr(argv0, '\\')+1);
    *strchr(defApplication, '.') = 0;

2452 2453 2454 2455
    map = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
                             4096, "winetest_shlexec_dde_map");
    shared_block = MapViewOfFile(map, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 4096);

2456
    ddeflags = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI;
2457 2458 2459
    test = dde_tests;
    while (test->command)
    {
2460 2461
        if (!create_test_association(".sde"))
        {
2462
            skip("Unable to create association for '.sde'\n");
2463 2464
            return;
        }
2465
        create_test_verb_dde("shlexec.sde", "Open", 0, test->command, test->ddeexec,
2466
                             test->application, test->topic, test->ifexec);
2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477

        if (test->application != NULL || test->topic != NULL)
        {
            strcpy(shared_block, test->application ? test->application : defApplication);
            strcpy(shared_block + strlen(shared_block) + 1, test->topic ? test->topic : SZDDESYS_TOPIC);
        }
        else
        {
            shared_block[0] = '\0';
            shared_block[1] = '\0';
        }
2478 2479
        ddeExec[0] = 0;

2480 2481 2482
        waitforinputidle_count = 0;
        dde_ready_event = CreateEventA(NULL, TRUE, FALSE, "winetest_shlexec_dde_ready");
        rc = shell_execute_ex(ddeflags, NULL, filename, NULL, NULL, NULL);
2483
        CloseHandle(dde_ready_event);
2484 2485 2486 2487
        if (!(ddeflags & SEE_MASK_WAITFORINPUTIDLE) && rc == SE_ERR_DDEFAIL &&
            GetLastError() == ERROR_FILE_NOT_FOUND &&
            strcmp(winetest_platform, "windows") == 0)
        {
2488 2489 2490 2491
            /* Windows 10 does not call WaitForInputIdle() for DDE which creates
             * a race condition as the DDE server may not have time to start up.
             * When that happens the test fails with the above results and we
             * compensate by forcing the WaitForInputIdle() call.
2492 2493 2494 2495
             */
            trace("Adding SEE_MASK_WAITFORINPUTIDLE for Windows 10\n");
            ddeflags |= SEE_MASK_WAITFORINPUTIDLE;
            delete_test_association(".sde");
2496
            Sleep(CHILD_DDE_TIMEOUT);
2497 2498
            continue;
        }
2499
        okShell(32 < rc, "failed: rc=%lu err=%u\n", rc, GetLastError());
2500
        if (test->ddeexec)
2501 2502 2503 2504
            okShell(waitforinputidle_count == 1 ||
                    broken(waitforinputidle_count == 0) /* Win10 race */,
                    "WaitForInputIdle() was called %u times\n",
                    waitforinputidle_count);
2505
        else
2506
            okShell(waitforinputidle_count == 0, "WaitForInputIdle() was called %u times for a non-DDE case\n", waitforinputidle_count);
2507

2508
        if (32 < rc)
2509
        {
2510 2511 2512 2513
            if (test->broken)
                okChildIntBroken("argcA", test->expectedArgs + 3);
            else
                okChildInt("argcA", test->expectedArgs + 3);
2514 2515 2516 2517 2518

            if (test->expectedArgs == 1) okChildPath("argvA3", filename);

            sprintf(params, test->expectedDdeExec, filename);
            okChildPath("ddeExec", params);
2519
        }
2520
        reset_association_description();
2521

2522 2523 2524 2525
        delete_test_association(".sde");
        test++;
    }

2526 2527 2528
    UnmapViewOfFile(shared_block);
    CloseHandle(map);
    hook_WaitForInputIdle((void *) WaitForInputIdle);
2529 2530
}

2531
#define DDE_DEFAULT_APP_VARIANTS 3
2532 2533 2534
typedef struct
{
    const char* command;
2535
    const char* expectedDdeApplication[DDE_DEFAULT_APP_VARIANTS];
2536
    int todo;
2537
    int rc[DDE_DEFAULT_APP_VARIANTS];
2538 2539 2540 2541
} dde_default_app_tests_t;

static dde_default_app_tests_t dde_default_app_tests[] =
{
2542 2543 2544 2545 2546 2547 2548
    /* There are three possible sets of results: Windows <= 2000, XP SP1 and
     * >= XP SP2. Use the first two tests to determine which results to expect.
     */

    /* Test unquoted existing filename with a space */
    {"%s\\test file.exe", {"test file", "test file", "test"}, 0x0, {33, 33, 33}},
    {"%s\\test2 file.exe", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2549

2550
    /* Test unquoted existing filename with a space */
2551
    {"%s\\test file.exe param", {"test file", "test file", "test"}, 0x0, {33, 33, 33}},
2552 2553

    /* Test quoted existing filename with a space */
2554 2555
    {"\"%s\\test file.exe\"", {"test file", "test file", "test file"}, 0x0, {33, 33, 33}},
    {"\"%s\\test file.exe\" param", {"test file", "test file", "test file"}, 0x0, {33, 33, 33}},
2556 2557 2558

    /* Test unquoted filename with a space that doesn't exist, but
     * test2.exe does */
2559
    {"%s\\test2 file.exe param", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2560 2561

    /* Test quoted filename with a space that does not exist */
2562 2563
    {"\"%s\\test2 file.exe\"", {"", "", "test2 file"}, 0x0, {5, 2, 33}},
    {"\"%s\\test2 file.exe\" param", {"", "", "test2 file"}, 0x0, {5, 2, 33}},
2564 2565

    /* Test filename supplied without the extension */
2566 2567
    {"%s\\test2", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
    {"%s\\test2 param", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2568

2569
    /* Test an unquoted nonexistent filename */
2570 2571
    {"%s\\notexist.exe", {"", "", "notexist"}, 0x0, {5, 2, 33}},
    {"%s\\notexist.exe param", {"", "", "notexist"}, 0x0, {5, 2, 33}},
2572 2573

    /* Test an application that will be found on the path */
2574 2575
    {"cmd", {"cmd", "cmd", "cmd"}, 0x0, {33, 33, 33}},
    {"cmd param", {"cmd", "cmd", "cmd"}, 0x0, {33, 33, 33}},
2576 2577

    /* Test an application that will not be found on the path */
2578 2579
    {"xyzwxyzwxyz", {"", "", "xyzwxyzwxyz"}, 0x0, {5, 2, 33}},
    {"xyzwxyzwxyz param", {"", "", "xyzwxyzwxyz"}, 0x0, {5, 2, 33}},
2580

2581
    {NULL, {NULL}, 0, {0}}
2582 2583
};

2584 2585 2586 2587 2588 2589 2590 2591 2592 2593
typedef struct
{
    char *filename;
    DWORD threadIdParent;
} dde_thread_info_t;

static DWORD CALLBACK ddeThread(LPVOID arg)
{
    dde_thread_info_t *info = arg;
    assert(info && info->filename);
2594 2595 2596 2597
    PostThreadMessageA(info->threadIdParent,
                       WM_QUIT,
                       shell_execute_ex(SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI, NULL, info->filename, NULL, NULL, NULL),
                       0);
2598 2599 2600
    ExitThread(0);
}

2601 2602 2603 2604 2605 2606 2607
static void test_dde_default_app(void)
{
    char filename[MAX_PATH];
    HSZ hszApplication;
    dde_thread_info_t info = { filename, GetCurrentThreadId() };
    const dde_default_app_tests_t* test;
    char params[1024];
2608
    DWORD threadId;
2609
    MSG msg;
2610 2611
    INT_PTR rc;
    int which = 0;
2612 2613
    HDDEDATA ret;
    BOOL b;
2614

2615
    post_quit_on_execute = FALSE;
2616 2617
    ddeInst = 0;
    rc = DdeInitializeA(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES |
2618 2619
                        CBF_FAIL_POKES | CBF_FAIL_REQUESTS, 0);
    ok(rc == DMLERR_NO_ERROR, "got %lx\n", rc);
2620 2621 2622 2623

    sprintf(filename, "%s\\test file.sde", tmpdir);

    /* It is strictly not necessary to register an application name here, but wine's
2624
     * DdeNameService implementation complains if 0 is passed instead of
2625 2626 2627
     * hszApplication with DNS_FILTEROFF */
    hszApplication = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI);
    hszTopic = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI);
2628 2629 2630
    ok(hszApplication && hszTopic, "got %p and %p\n", hszApplication, hszTopic);
    ret = DdeNameService(ddeInst, hszApplication, 0, DNS_REGISTER | DNS_FILTEROFF);
    ok(ret != 0, "got %p\n", ret);
2631 2632 2633 2634

    test = dde_default_app_tests;
    while (test->command)
    {
2635 2636
        HANDLE thread;

2637 2638 2639 2640 2641
        if (!create_test_association(".sde"))
        {
            skip("Unable to create association for '.sde'\n");
            return;
        }
2642
        sprintf(params, test->command, tmpdir);
2643
        create_test_verb_dde("shlexec.sde", "Open", 1, params, "[test]", NULL,
2644 2645 2646 2647 2648 2649 2650
                             "shlexec", NULL);
        ddeApplication[0] = 0;

        /* No application will be run as we will respond to the first DDE event,
         * so don't wait for it */
        SetEvent(hEvent);

2651 2652
        thread = CreateThread(NULL, 0, ddeThread, &info, 0, &threadId);
        ok(thread != NULL, "got %p\n", thread);
2653
        while (GetMessageA(&msg, NULL, 0, 0)) DispatchMessageA(&msg);
2654
        rc = msg.wParam > 32 ? 33 : msg.wParam;
2655

2656 2657 2658 2659 2660
        /* The first two tests determine which set of results to expect.
         * First check the platform as only the first set of results is
         * acceptable for Wine.
         */
        if (strcmp(winetest_platform, "wine"))
2661
        {
2662
            if (test == dde_default_app_tests)
2663
            {
2664 2665 2666 2667 2668 2669 2670 2671
                if (strcmp(ddeApplication, test->expectedDdeApplication[0]))
                    which = 2;
            }
            else if (test == dde_default_app_tests + 1)
            {
                if (which == 0 && rc == test->rc[1])
                    which = 1;
                trace("DDE result variant %d\n", which);
2672 2673 2674
            }
        }

2675
        todo_wine_if(test->todo & 0x1)
2676 2677
            okShell(rc==test->rc[which], "failed: rc=%lu err=%u\n",
                    rc, GetLastError());
2678 2679
        if (rc == 33)
        {
2680
            todo_wine_if(test->todo & 0x2)
2681
                ok(!strcmp(ddeApplication, test->expectedDdeApplication[which]),
2682
                   "Expected application '%s', got '%s'\n",
2683
                   test->expectedDdeApplication[which], ddeApplication);
2684
        }
2685
        reset_association_description();
2686

2687 2688 2689 2690
        delete_test_association(".sde");
        test++;
    }

2691 2692 2693 2694 2695 2696 2697 2698
    ret = DdeNameService(ddeInst, hszApplication, 0, DNS_UNREGISTER);
    ok(ret != 0, "got %p\n", ret);
    b = DdeFreeStringHandle(ddeInst, hszTopic);
    ok(b, "got %d\n", b);
    b = DdeFreeStringHandle(ddeInst, hszApplication);
    ok(b, "got %d\n", b);
    b = DdeUninitialize(ddeInst);
    ok(b, "got %d\n", b);
2699
}
2700

2701
static void init_test(void)
2702
{
2703 2704
    HMODULE hdll;
    HRESULT (WINAPI *pDllGetVersion)(DLLVERSIONINFO*);
2705 2706
    char filename[MAX_PATH];
    WCHAR lnkfile[MAX_PATH];
2707
    char params[1024];
2708 2709 2710 2711 2712
    const char* const * testfile;
    lnk_desc_t desc;
    DWORD rc;
    HRESULT r;

2713 2714 2715 2716 2717 2718
    hdll=GetModuleHandleA("shell32.dll");
    pDllGetVersion=(void*)GetProcAddress(hdll, "DllGetVersion");
    if (pDllGetVersion)
    {
        dllver.cbSize=sizeof(dllver);
        pDllGetVersion(&dllver);
2719
        trace("major=%d minor=%d build=%d platform=%d\n",
2720 2721 2722 2723 2724 2725 2726 2727
              dllver.dwMajorVersion, dllver.dwMinorVersion,
              dllver.dwBuildNumber, dllver.dwPlatformID);
    }
    else
    {
        memset(&dllver, 0, sizeof(dllver));
    }

2728
    r = CoInitialize(NULL);
2729
    ok(r == S_OK, "CoInitialize failed (0x%08x)\n", r);
2730
    if (FAILED(r))
2731 2732
        exit(1);

2733
    rc=GetModuleFileNameA(NULL, argv0, sizeof(argv0));
2734
    ok(rc != 0 && rc < sizeof(argv0), "got %d\n", rc);
2735
    if (GetFileAttributesA(argv0)==INVALID_FILE_ATTRIBUTES)
2736 2737
    {
        strcat(argv0, ".so");
2738
        ok(GetFileAttributesA(argv0)!=INVALID_FILE_ATTRIBUTES,
2739 2740 2741
           "unable to find argv0!\n");
    }

2742 2743 2744 2745 2746 2747
    /* Older versions (win 2k) fail tests if there is a space in
       the path. */
    if (dllver.dwMajorVersion <= 5)
        strcpy(filename, "c:\\");
    else
        GetTempPathA(sizeof(filename), filename);
2748
    GetTempFileNameA(filename, "wt", 0, tmpdir);
2749
    GetLongPathNameA(tmpdir, tmpdir, sizeof(tmpdir));
2750 2751 2752
    DeleteFileA( tmpdir );
    rc = CreateDirectoryA( tmpdir, NULL );
    ok( rc, "failed to create %s err %u\n", tmpdir, GetLastError() );
2753 2754 2755
    /* Set %TMPDIR% for the tests */
    SetEnvironmentVariableA("TMPDIR", tmpdir);

2756
    rc = GetTempFileNameA(tmpdir, "wt", 0, child_file);
2757
    ok(rc != 0, "got %d\n", rc);
2758
    init_event(child_file);
2759 2760 2761 2762 2763 2764 2765 2766

    /* Set up the test files */
    testfile=testfiles;
    while (*testfile)
    {
        HANDLE hfile;

        sprintf(filename, *testfile, tmpdir);
2767
        hfile=CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
2768 2769 2770
                     FILE_ATTRIBUTE_NORMAL, NULL);
        if (hfile==INVALID_HANDLE_VALUE)
        {
2771
            trace("unable to create '%s': err=%u\n", filename, GetLastError());
2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785
            assert(0);
        }
        CloseHandle(hfile);
        testfile++;
    }

    /* Setup the test shortcuts */
    sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
    MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, sizeof(lnkfile)/sizeof(*lnkfile));
    desc.description=NULL;
    desc.workdir=NULL;
    sprintf(filename, "%s\\test file.shlexec", tmpdir);
    desc.path=filename;
    desc.pidl=NULL;
2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800
    desc.arguments="ignored";
    desc.showcmd=0;
    desc.icon=NULL;
    desc.icon_id=0;
    desc.hotkey=0;
    create_lnk(lnkfile, &desc, 0);

    sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
    MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, sizeof(lnkfile)/sizeof(*lnkfile));
    desc.description=NULL;
    desc.workdir=NULL;
    desc.path=argv0;
    desc.pidl=NULL;
    sprintf(params, "shlexec \"%s\" Lnk", child_file);
    desc.arguments=params;
2801 2802 2803 2804 2805 2806 2807
    desc.showcmd=0;
    desc.icon=NULL;
    desc.icon_id=0;
    desc.hotkey=0;
    create_lnk(lnkfile, &desc, 0);

    /* Create a basic association suitable for most tests */
2808 2809
    if (!create_test_association(".shlexec"))
    {
2810
        skip_shlexec_tests = TRUE;
2811 2812 2813
        skip("Unable to create association for '.shlexec'\n");
        return;
    }
2814 2815 2816 2817 2818 2819 2820
    create_test_verb("shlexec.shlexec", "Open", 0, "Open \"%1\"");
    create_test_verb("shlexec.shlexec", "NoQuotes", 0, "NoQuotes %1");
    create_test_verb("shlexec.shlexec", "LowerL", 0, "LowerL %l");
    create_test_verb("shlexec.shlexec", "QuotedLowerL", 0, "QuotedLowerL \"%l\"");
    create_test_verb("shlexec.shlexec", "UpperL", 0, "UpperL %L");
    create_test_verb("shlexec.shlexec", "QuotedUpperL", 0, "QuotedUpperL \"%L\"");

2821 2822 2823
    create_test_association(".sha");
    create_test_verb("shlexec.sha", "averb", 0, "AVerb \"%1\"");

2824 2825 2826
    create_test_class("shlproto", TRUE);
    create_test_verb("shlproto", "open", 0, "URL \"%1\"");
    create_test_verb("shlproto", "averb", 0, "AVerb \"%1\"");
2827 2828 2829

    /* Set an environment variable to see if it is inherited */
    SetEnvironmentVariableA("ShlexecVar", "Present");
2830 2831
}

2832
static void cleanup_test(void)
2833 2834 2835 2836 2837 2838 2839 2840 2841
{
    char filename[MAX_PATH];
    const char* const * testfile;

    /* Delete the test files */
    testfile=testfiles;
    while (*testfile)
    {
        sprintf(filename, *testfile, tmpdir);
2842
        /* Make sure we can delete the files ('test file.noassoc' is read-only now) */
2843 2844
        SetFileAttributesA(filename, FILE_ATTRIBUTE_NORMAL);
        DeleteFileA(filename);
2845 2846
        testfile++;
    }
2847
    DeleteFileA(child_file);
2848
    RemoveDirectoryA(tmpdir);
2849 2850 2851

    /* Delete the test association */
    delete_test_association(".shlexec");
2852
    delete_test_association(".sha");
2853
    delete_test_class("shlproto");
2854

2855 2856
    CloseHandle(hEvent);

2857 2858 2859
    CoUninitialize();
}

2860 2861
static void test_directory(void)
{
2862 2863
    char path[MAX_PATH], curdir[MAX_PATH];
    char params[1024], dirpath[1024];
2864
    INT_PTR rc;
2865

2866
    sprintf(path, "%s\\test2.exe", tmpdir);
2867 2868 2869 2870
    CopyFileA(argv0, path, FALSE);

    sprintf(params, "shlexec \"%s\" Exec", child_file);

2871 2872 2873
    /* Test with the current directory */
    GetCurrentDirectoryA(sizeof(curdir), curdir);
    SetCurrentDirectoryA(tmpdir);
2874
    rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2875
                        NULL, "test2.exe", params, NULL, NULL);
2876
    okShell(rc > 32, "returned %lu\n", rc);
2877 2878 2879 2880 2881 2882 2883
    okChildInt("argcA", 4);
    okChildString("argvA3", "Exec");
    todo_wine okChildPath("longPath", path);
    SetCurrentDirectoryA(curdir);

    rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
                        NULL, "test2.exe", params, NULL, NULL);
2884
    okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2885 2886 2887 2888

    /* Explicitly specify the directory to use */
    rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
                        NULL, "test2.exe", params, tmpdir, NULL);
2889
    okShell(rc > 32, "returned %lu\n", rc);
2890 2891 2892
    okChildInt("argcA", 4);
    okChildString("argvA3", "Exec");
    todo_wine okChildPath("longPath", path);
2893

2894
    /* Specify it through an environment variable */
2895
    rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2896
                        NULL, "test2.exe", params, "%TMPDIR%", NULL);
2897
    todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2898 2899 2900

    rc=shell_execute_ex(SEE_MASK_DOENVSUBST|SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
                        NULL, "test2.exe", params, "%TMPDIR%", NULL);
2901
    okShell(rc > 32, "returned %lu\n", rc);
2902 2903 2904 2905
    okChildInt("argcA", 4);
    okChildString("argvA3", "Exec");
    todo_wine okChildPath("longPath", path);

2906 2907 2908 2909
    /* Not a colon-separated directory list */
    sprintf(dirpath, "%s:%s", curdir, tmpdir);
    rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
                        NULL, "test2.exe", params, dirpath, NULL);
2910
    okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2911 2912
}

2913 2914 2915 2916
START_TEST(shlexec)
{

    myARGC = winetest_get_mainargs(&myARGV);
2917
    if (myARGC >= 3)
2918
    {
2919
        doChild(myARGC, myARGV);
2920 2921
        /* Skip the tests/failures trace for child processes */
        ExitProcess(winetest_get_failures());
2922 2923 2924 2925
    }

    init_test();

2926
    test_commandline2argv();
2927
    test_argify();
2928
    test_lpFile_parsed();
2929
    test_filename();
2930
    test_fileurls();
2931
    test_urls();
2932
    test_find_executable();
2933
    test_lnks();
2934
    test_exes();
2935
    test_dde();
2936
    test_dde_default_app();
2937
    test_directory();
2938 2939 2940

    cleanup_test();
}