|  | /* | 
|  | * INF file parsing tests | 
|  | * | 
|  | * Copyright 2002, 2005 Alexandre Julliard for CodeWeavers | 
|  | * | 
|  | * 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 | 
|  | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
|  | */ | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <stdarg.h> | 
|  |  | 
|  | #include "windef.h" | 
|  | #include "winbase.h" | 
|  | #include "wingdi.h" | 
|  | #include "winuser.h" | 
|  | #include "winreg.h" | 
|  | #include "setupapi.h" | 
|  |  | 
|  | #include "wine/test.h" | 
|  |  | 
|  | /* function pointers */ | 
|  | static HMODULE hSetupAPI; | 
|  | static LPCSTR (WINAPI *pSetupGetFieldA)(PINFCONTEXT,DWORD); | 
|  | static LPCWSTR (WINAPI *pSetupGetFieldW)(PINFCONTEXT,DWORD); | 
|  | static BOOL (WINAPI *pSetupEnumInfSectionsA)( HINF hinf, UINT index, PSTR buffer, DWORD size, UINT *need ); | 
|  |  | 
|  | static void init_function_pointers(void) | 
|  | { | 
|  | hSetupAPI = GetModuleHandleA("setupapi.dll"); | 
|  |  | 
|  | /* Nice, pSetupGetField is either A or W depending on the Windows version! The actual test | 
|  | * takes care of this difference */ | 
|  | pSetupGetFieldA = (void *)GetProcAddress(hSetupAPI, "pSetupGetField"); | 
|  | pSetupGetFieldW = (void *)GetProcAddress(hSetupAPI, "pSetupGetField"); | 
|  | pSetupEnumInfSectionsA = (void *)GetProcAddress(hSetupAPI, "SetupEnumInfSectionsA" ); | 
|  | } | 
|  |  | 
|  | static const char tmpfilename[] = ".\\tmp.inf"; | 
|  |  | 
|  | /* some large strings */ | 
|  | #define A255 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \ | 
|  | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \ | 
|  | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \ | 
|  | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" | 
|  | #define A256 "a" A255 | 
|  | #define A400 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \ | 
|  | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \ | 
|  | "aaaaaaaaaaaaaaaa" A256 | 
|  | #define A1200 A400 A400 A400 | 
|  | #define A511 A255 A256 | 
|  | #define A4097 "a" A256 A256 A256 A256 A256 A256 A256 A256 A256 A256 A256 A256 A256 A256 A256 A256 | 
|  |  | 
|  | #define STD_HEADER "[Version]\r\nSignature=\"$CHICAGO$\"\r\n" | 
|  |  | 
|  | #define STR_SECTION "[Strings]\nfoo=aaa\nbar=bbb\nloop=%loop2%\nloop2=%loop%\n" \ | 
|  | "per%%cent=abcd\nper=1\ncent=2\n22=foo\n" \ | 
|  | "big=" A400 "\n" \ | 
|  | "mydrive=\"C:\\\"\n" \ | 
|  | "verybig=" A1200 "\n" | 
|  |  | 
|  | /* create a new file with specified contents and open it */ | 
|  | static HINF test_file_contents( const char *data, UINT *err_line ) | 
|  | { | 
|  | DWORD res; | 
|  | HANDLE handle = CreateFileA( tmpfilename, GENERIC_READ|GENERIC_WRITE, | 
|  | FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, 0 ); | 
|  | if (handle == INVALID_HANDLE_VALUE) return 0; | 
|  | if (!WriteFile( handle, data, strlen(data), &res, NULL )) trace( "write error\n" ); | 
|  | CloseHandle( handle ); | 
|  | return SetupOpenInfFileA( tmpfilename, 0, INF_STYLE_WIN4, err_line ); | 
|  | } | 
|  |  | 
|  | static const char *get_string_field( INFCONTEXT *context, DWORD index ) | 
|  | { | 
|  | static char buffer[MAX_INF_STRING_LENGTH+32]; | 
|  | if (SetupGetStringFieldA( context, index, buffer, sizeof(buffer), NULL )) return buffer; | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static const char *get_line_text( INFCONTEXT *context ) | 
|  | { | 
|  | static char buffer[MAX_INF_STRING_LENGTH+32]; | 
|  | if (SetupGetLineTextA( context, 0, 0, 0, buffer, sizeof(buffer), NULL )) return buffer; | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Test various valid/invalid file formats */ | 
|  |  | 
|  | static const struct | 
|  | { | 
|  | const char *data; | 
|  | DWORD error; | 
|  | UINT err_line; | 
|  | int todo; | 
|  | } invalid_files[] = | 
|  | { | 
|  | /* file contents                                         expected error (or 0)     errline  todo */ | 
|  | { "\r\n",                                                ERROR_WRONG_INF_STYLE,       0,    0 }, | 
|  | { "abcd\r\n",                                            ERROR_WRONG_INF_STYLE,       0,    1 }, | 
|  | { "[Version]\r\n",                                       ERROR_WRONG_INF_STYLE,       0,    0 }, | 
|  | { "[Version]\nSignature=",                               ERROR_WRONG_INF_STYLE,       0,    0 }, | 
|  | { "[Version]\nSignature=foo",                            ERROR_WRONG_INF_STYLE,       0,    0 }, | 
|  | { "[version]\nsignature=$chicago$",                      0,                           0,    0 }, | 
|  | { "[VERSION]\nSIGNATURE=$CHICAGO$",                      0,                           0,    0 }, | 
|  | { "[Version]\nSignature=$chicago$,abcd",                 0,                           0,    0 }, | 
|  | { "[Version]\nabc=def\nSignature=$chicago$",             0,                           0,    0 }, | 
|  | { "[Version]\nabc=def\n[Version]\nSignature=$chicago$",  0,                           0,    0 }, | 
|  | { STD_HEADER,                                            0,                           0,    0 }, | 
|  | { STD_HEADER "[]\r\n",                                   0,                           0,    0 }, | 
|  | { STD_HEADER "]\r\n",                                    0,                           0,    0 }, | 
|  | { STD_HEADER "[" A255 "]\r\n",                           0,                           0,    0 }, | 
|  | { STD_HEADER "[ab\r\n",                                  ERROR_BAD_SECTION_NAME_LINE, 3,    0 }, | 
|  | { STD_HEADER "\n\n[ab\x1a]\n",                           ERROR_BAD_SECTION_NAME_LINE, 5,    0 }, | 
|  | { STD_HEADER "[" A256 "]\r\n",                           ERROR_SECTION_NAME_TOO_LONG, 3,    0 }, | 
|  | { "[abc]\n" STD_HEADER,                                  0,                           0,    0 }, | 
|  | { "abc\r\n" STD_HEADER,                                  ERROR_EXPECTED_SECTION_NAME, 1,    0 }, | 
|  | { ";\n;\nabc\r\n" STD_HEADER,                            ERROR_EXPECTED_SECTION_NAME, 3,    0 }, | 
|  | { ";\n;\nab\nab\n" STD_HEADER,                           ERROR_EXPECTED_SECTION_NAME, 3,    0 }, | 
|  | { ";aa\n;bb\n" STD_HEADER,                               0,                           0,    0 }, | 
|  | { STD_HEADER " [TestSection\x00]\n",                     ERROR_BAD_SECTION_NAME_LINE, 3,    0 }, | 
|  | { STD_HEADER " [Test\x00Section]\n",                     ERROR_BAD_SECTION_NAME_LINE, 3,    0 }, | 
|  | { STD_HEADER " [TestSection\x00]\n",                     ERROR_BAD_SECTION_NAME_LINE, 3,    0 }, | 
|  | { STD_HEADER " [Test\x00Section]\n",                     ERROR_BAD_SECTION_NAME_LINE, 3,    0 }, | 
|  | }; | 
|  |  | 
|  | static void test_invalid_files(void) | 
|  | { | 
|  | unsigned int i; | 
|  | UINT err_line; | 
|  | HINF hinf; | 
|  | DWORD err; | 
|  |  | 
|  | for (i = 0; i < sizeof(invalid_files)/sizeof(invalid_files[0]); i++) | 
|  | { | 
|  | SetLastError( 0xdeadbeef ); | 
|  | err_line = 0xdeadbeef; | 
|  | hinf = test_file_contents( invalid_files[i].data, &err_line ); | 
|  | err = GetLastError(); | 
|  | trace( "hinf=%p err=0x%x line=%d\n", hinf, err, err_line ); | 
|  | if (invalid_files[i].error)  /* should fail */ | 
|  | { | 
|  | ok( hinf == INVALID_HANDLE_VALUE, "file %u: Open succeeded\n", i ); | 
|  | if (invalid_files[i].todo) todo_wine | 
|  | { | 
|  | ok( err == invalid_files[i].error, "file %u: Bad error %u/%u\n", | 
|  | i, err, invalid_files[i].error ); | 
|  | ok( err_line == invalid_files[i].err_line, "file %u: Bad error line %d/%d\n", | 
|  | i, err_line, invalid_files[i].err_line ); | 
|  | } | 
|  | else | 
|  | { | 
|  | ok( err == invalid_files[i].error, "file %u: Bad error %u/%u\n", | 
|  | i, err, invalid_files[i].error ); | 
|  | ok( err_line == invalid_files[i].err_line, "file %u: Bad error line %d/%d\n", | 
|  | i, err_line, invalid_files[i].err_line ); | 
|  | } | 
|  | } | 
|  | else  /* should succeed */ | 
|  | { | 
|  | ok( hinf != INVALID_HANDLE_VALUE, "file %u: Open failed\n", i ); | 
|  | ok( err == 0, "file %u: Error code set to %u\n", i, err ); | 
|  | } | 
|  | SetupCloseInfFile( hinf ); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Test various section names */ | 
|  |  | 
|  | static const struct | 
|  | { | 
|  | const char *data; | 
|  | const char *section; | 
|  | DWORD error; | 
|  | } section_names[] = | 
|  | { | 
|  | /* file contents                              section name       error code */ | 
|  | { STD_HEADER "[TestSection]",                 "TestSection",         0 }, | 
|  | { STD_HEADER "[TestSection]\n",               "TestSection",         0 }, | 
|  | { STD_HEADER "[TESTSECTION]\r\n",             "TestSection",         0 }, | 
|  | { STD_HEADER "[TestSection]\n[abc]",          "testsection",         0 }, | 
|  | { STD_HEADER ";[TestSection]\n",              "TestSection",         ERROR_SECTION_NOT_FOUND }, | 
|  | { STD_HEADER "[TestSection]\n",               "Bad name",            ERROR_SECTION_NOT_FOUND }, | 
|  | /* spaces */ | 
|  | { STD_HEADER "[TestSection]   \r\n",          "TestSection",         0 }, | 
|  | { STD_HEADER "   [TestSection]\r\n",          "TestSection",         0 }, | 
|  | { STD_HEADER "   [TestSection]   dummy\r\n",  "TestSection",         0 }, | 
|  | { STD_HEADER "   [TestSection]   [foo]\r\n",  "TestSection",         0 }, | 
|  | { STD_HEADER " [  Test Section  ] dummy\r\n", "  Test Section  ",    0 }, | 
|  | { STD_HEADER "[TestSection] \032\ndummy",     "TestSection",         0 }, | 
|  | { STD_HEADER "[TestSection] \n\032dummy",     "TestSection",         0 }, | 
|  | /* special chars in section name */ | 
|  | { STD_HEADER "[Test[Section]\r\n",            "Test[Section",        0 }, | 
|  | { STD_HEADER "[Test[S]ection]\r\n",           "Test[S",              0 }, | 
|  | { STD_HEADER "[Test[[[Section]\r\n",          "Test[[[Section",      0 }, | 
|  | { STD_HEADER "[]\r\n",                        "",                    0 }, | 
|  | { STD_HEADER "[[[]\n",                        "[[",                  0 }, | 
|  | { STD_HEADER "[Test\"Section]\r\n",           "Test\"Section",       0 }, | 
|  | { STD_HEADER "[Test\\Section]\r\n",           "Test\\Section",       0 }, | 
|  | { STD_HEADER "[Test\\ Section]\r\n",          "Test\\ Section",      0 }, | 
|  | { STD_HEADER "[Test;Section]\r\n",            "Test;Section",        0 }, | 
|  | /* various control chars */ | 
|  | { STD_HEADER " [Test\r\b\tSection] \n",       "Test\r\b\tSection", 0 }, | 
|  | /* nulls */ | 
|  | }; | 
|  |  | 
|  | static void test_section_names(void) | 
|  | { | 
|  | unsigned int i; | 
|  | UINT err_line; | 
|  | HINF hinf; | 
|  | DWORD err; | 
|  | LONG ret; | 
|  |  | 
|  | for (i = 0; i < sizeof(section_names)/sizeof(section_names[0]); i++) | 
|  | { | 
|  | SetLastError( 0xdeadbeef ); | 
|  | hinf = test_file_contents( section_names[i].data, &err_line ); | 
|  | ok( hinf != INVALID_HANDLE_VALUE, "line %u: open failed err %u\n", i, GetLastError() ); | 
|  | if (hinf == INVALID_HANDLE_VALUE) continue; | 
|  |  | 
|  | ret = SetupGetLineCountA( hinf, section_names[i].section ); | 
|  | err = GetLastError(); | 
|  | trace( "hinf=%p ret=%d err=0x%x\n", hinf, ret, err ); | 
|  | if (ret != -1) | 
|  | { | 
|  | ok( !section_names[i].error, "line %u: section name %s found\n", | 
|  | i, section_names[i].section ); | 
|  | ok( !err, "line %u: bad error code %u\n", i, err ); | 
|  | } | 
|  | else | 
|  | { | 
|  | ok( section_names[i].error, "line %u: section name %s not found\n", | 
|  | i, section_names[i].section ); | 
|  | ok( err == section_names[i].error, "line %u: bad error %u/%u\n", | 
|  | i, err, section_names[i].error ); | 
|  | } | 
|  | SetupCloseInfFile( hinf ); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void test_enum_sections(void) | 
|  | { | 
|  | static const char *contents = STD_HEADER "[s1]\nfoo=bar\n[s2]\nbar=foo\n[s3]\n[strings]\na=b\n"; | 
|  |  | 
|  | BOOL ret; | 
|  | DWORD len; | 
|  | HINF hinf; | 
|  | UINT err, index; | 
|  | char buffer[256]; | 
|  |  | 
|  | if (!pSetupEnumInfSectionsA) | 
|  | { | 
|  | win_skip( "SetupEnumInfSectionsA not available\n" ); | 
|  | return; | 
|  | } | 
|  |  | 
|  | hinf = test_file_contents( contents, &err ); | 
|  | ok( hinf != NULL, "Expected valid INF file\n" ); | 
|  |  | 
|  | for (index = 0; ; index++) | 
|  | { | 
|  | SetLastError( 0xdeadbeef ); | 
|  | ret = pSetupEnumInfSectionsA( hinf, index, NULL, 0, &len ); | 
|  | err = GetLastError(); | 
|  | if (!ret && GetLastError() == ERROR_NO_MORE_ITEMS) break; | 
|  | ok( ret, "SetupEnumInfSectionsA failed\n" ); | 
|  | ok( len == 3 || len == 8, "wrong len %u\n", len ); | 
|  |  | 
|  | SetLastError( 0xdeadbeef ); | 
|  | ret = pSetupEnumInfSectionsA( hinf, index, NULL, sizeof(buffer), &len ); | 
|  | err = GetLastError(); | 
|  | ok( !ret, "SetupEnumInfSectionsA succeeded\n" ); | 
|  | ok( err == ERROR_INVALID_USER_BUFFER, "wrong error %u\n", err ); | 
|  | ok( len == 3 || len == 8, "wrong len %u\n", len ); | 
|  |  | 
|  | SetLastError( 0xdeadbeef ); | 
|  | ret = pSetupEnumInfSectionsA( hinf, index, buffer, sizeof(buffer), &len ); | 
|  | ok( ret, "SetupEnumInfSectionsA failed err %u\n", GetLastError() ); | 
|  | ok( len == 3 || len == 8, "wrong len %u\n", len ); | 
|  | ok( !lstrcmpi( buffer, "version" ) || !lstrcmpi( buffer, "s1" ) || | 
|  | !lstrcmpi( buffer, "s2" ) || !lstrcmpi( buffer, "s3" ) || !lstrcmpi( buffer, "strings" ), | 
|  | "bad section '%s'\n", buffer ); | 
|  | } | 
|  | SetupCloseInfFile( hinf ); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Test various key and value names */ | 
|  |  | 
|  | static const struct | 
|  | { | 
|  | const char *data; | 
|  | const char *key; | 
|  | const char *fields[10]; | 
|  | } key_names[] = | 
|  | { | 
|  | /* file contents          expected key       expected fields */ | 
|  | { "ab=cd",                "ab",            { "cd" } }, | 
|  | { "ab=cd,ef,gh,ij",       "ab",            { "cd", "ef", "gh", "ij" } }, | 
|  | { "ab",                   "ab",            { "ab" } }, | 
|  | { "ab,cd",                NULL,            { "ab", "cd" } }, | 
|  | { "ab,cd=ef",             NULL,            { "ab", "cd=ef" } }, | 
|  | { "=abcd,ef",             "",              { "abcd", "ef" } }, | 
|  | /* backslashes */ | 
|  | { "ba\\\ncd=ef",          "bacd",          { "ef" } }, | 
|  | { "ab  \\  \ncd=ef",      "abcd",          { "ef" } }, | 
|  | { "ab\\\ncd,ef",          NULL,            { "abcd", "ef" } }, | 
|  | { "ab  \\ ;cc\ncd=ef",    "abcd",          { "ef" } }, | 
|  | { "ab \\ \\ \ncd=ef",     "abcd",          { "ef" } }, | 
|  | { "ba \\ dc=xx",          "ba \\ dc",      { "xx" } }, | 
|  | { "ba \\\\ \nc=d",        "bac",           { "d" } }, | 
|  | { "a=b\\\\c",             "a",             { "b\\\\c" } }, | 
|  | { "ab=cd \\ ",            "ab",            { "cd" } }, | 
|  | { "ba=c \\ \n \\ \n a",   "ba",            { "ca" } }, | 
|  | { "ba=c \\ \n \\ a",      "ba",            { "c\\ a" } }, | 
|  | { "  \\ a= \\ b",         "\\ a",          { "\\ b" } }, | 
|  | /* quotes */ | 
|  | { "Ab\"Cd\"=Ef",          "AbCd",          { "Ef" } }, | 
|  | { "Ab\"Cd=Ef\"",          "AbCd=Ef",       { "AbCd=Ef" } }, | 
|  | { "ab\"\"\"cd,ef=gh\"",   "ab\"cd,ef=gh",  { "ab\"cd,ef=gh" } }, | 
|  | { "ab\"\"cd=ef",          "abcd",          { "ef" } }, | 
|  | { "ab\"\"cd=ef,gh",       "abcd",          { "ef", "gh" } }, | 
|  | { "ab=cd\"\"ef",          "ab",            { "cdef" } }, | 
|  | { "ab=cd\",\"ef",         "ab",            { "cd,ef" } }, | 
|  | { "ab=cd\",ef",           "ab",            { "cd,ef" } }, | 
|  | { "ab=cd\",ef\\\nab",     "ab",            { "cd,ef\\" } }, | 
|  |  | 
|  | /* single quotes (unhandled)*/ | 
|  | { "HKLM,A,B,'C',D",       NULL,            { "HKLM", "A","B","'C'","D" } }, | 
|  | /* spaces */ | 
|  | { " a b = c , d \n",      "a b",           { "c", "d" } }, | 
|  | { " a b = c ,\" d\" \n",  "a b",           { "c", " d" } }, | 
|  | { " a b\r = c\r\n",       "a b",           { "c" } }, | 
|  | /* empty fields */ | 
|  | { "a=b,,,c,,,d",          "a",             { "b", "", "", "c", "", "", "d" } }, | 
|  | { "a=b,\"\",c,\" \",d",   "a",             { "b", "", "c", " ", "d" } }, | 
|  | { "=,,b",                 "",              { "", "", "b" } }, | 
|  | { ",=,,b",                NULL,            { "", "=", "", "b" } }, | 
|  | { "a=\n",                 "a",             { "" } }, | 
|  | { "=",                    "",              { "" } }, | 
|  | /* eof */ | 
|  | { "ab=c\032d",            "ab",            { "c" } }, | 
|  | { "ab\032=cd",            "ab",            { "ab" } }, | 
|  | /* nulls */ | 
|  | { "abcd=ef\x0gh",         "abcd",          { "ef" } }, | 
|  | /* multiple sections with same name */ | 
|  | { "[Test2]\nab\n[Test]\nee=ff\n",  "ee",    { "ff" } }, | 
|  | /* string substitution */ | 
|  | { "%foo%=%bar%\n" STR_SECTION,     "aaa",   { "bbb" } }, | 
|  | { "%foo%xx=%bar%yy\n" STR_SECTION, "aaaxx", { "bbbyy" } }, | 
|  | { "%% %foo%=%bar%\n" STR_SECTION,  "% aaa", { "bbb" } }, | 
|  | { "%f\"o\"o%=ccc\n" STR_SECTION,   "aaa",   { "ccc" } }, | 
|  | { "abc=%bar;bla%\n" STR_SECTION,   "abc",   { "%bar" } }, | 
|  | { "loop=%loop%\n" STR_SECTION,     "loop",  { "%loop2%" } }, | 
|  | { "%per%%cent%=100\n" STR_SECTION, "12",    { "100" } }, | 
|  | { "a=%big%\n" STR_SECTION,         "a",     { A400 } }, | 
|  | { "a=%verybig%\n" STR_SECTION,     "a",     { A511 } },  /* truncated to 511, not on Vista/W2K8 */ | 
|  | { "a=%big%%big%%big%%big%\n" STR_SECTION,   "a", { A400 A400 A400 A400 } }, | 
|  | { "a=%big%%big%%big%%big%%big%%big%%big%%big%%big%\n" STR_SECTION,   "a", { A400 A400 A400 A400 A400 A400 A400 A400 A400 } }, | 
|  | { "a=%big%%big%%big%%big%%big%%big%%big%%big%%big%%big%%big%\n" STR_SECTION,   "a", { A4097 /*MAX_INF_STRING_LENGTH+1*/ } }, | 
|  |  | 
|  | /* Prove expansion of system entries removes extra \'s and string | 
|  | replacements doesn't                                            */ | 
|  | { "ab=\"%24%\"\n" STR_SECTION,           "ab", { "C:\\" } }, | 
|  | { "ab=\"%mydrive%\"\n" STR_SECTION,      "ab", { "C:\\" } }, | 
|  | { "ab=\"%24%\\fred\"\n" STR_SECTION,     "ab", { "C:\\fred" } }, | 
|  | { "ab=\"%mydrive%\\fred\"\n" STR_SECTION,"ab", { "C:\\\\fred" } }, | 
|  | /* Confirm duplicate \'s kept */ | 
|  | { "ab=\"%24%\\\\fred\"",      "ab",            { "C:\\\\fred" } }, | 
|  | { "ab=C:\\\\FRED",            "ab",            { "C:\\\\FRED" } }, | 
|  | }; | 
|  |  | 
|  | /* check the key of a certain line */ | 
|  | static const char *check_key( INFCONTEXT *context, const char *wanted ) | 
|  | { | 
|  | const char *key = get_string_field( context, 0 ); | 
|  | DWORD err = GetLastError(); | 
|  |  | 
|  | if (!key) | 
|  | { | 
|  | ok( !wanted, "missing key %s\n", wanted ); | 
|  | ok( err == 0 || err == ERROR_INVALID_PARAMETER, "last error set to %u\n", err ); | 
|  | } | 
|  | else | 
|  | { | 
|  | ok( !strcmp( key, wanted ), "bad key %s/%s\n", key, wanted ); | 
|  | ok( err == 0, "last error set to %u\n", err ); | 
|  | } | 
|  | return key; | 
|  | } | 
|  |  | 
|  | static void test_key_names(void) | 
|  | { | 
|  | char buffer[MAX_INF_STRING_LENGTH+32]; | 
|  | const char *key, *line; | 
|  | unsigned int i, index, count; | 
|  | UINT err_line; | 
|  | HINF hinf; | 
|  | DWORD err; | 
|  | BOOL ret; | 
|  | INFCONTEXT context; | 
|  |  | 
|  | for (i = 0; i < sizeof(key_names)/sizeof(key_names[0]); i++) | 
|  | { | 
|  | strcpy( buffer, STD_HEADER "[Test]\n" ); | 
|  | strcat( buffer, key_names[i].data ); | 
|  | SetLastError( 0xdeadbeef ); | 
|  | hinf = test_file_contents( buffer, &err_line ); | 
|  | ok( hinf != INVALID_HANDLE_VALUE, "line %u: open failed err %u\n", i, GetLastError() ); | 
|  | if (hinf == INVALID_HANDLE_VALUE) continue; | 
|  |  | 
|  | ret = SetupFindFirstLineA( hinf, "Test", 0, &context ); | 
|  | assert( ret ); | 
|  |  | 
|  | key = check_key( &context, key_names[i].key ); | 
|  |  | 
|  | buffer[0] = buffer[1] = 0;  /* build the full line */ | 
|  | for (index = 0; ; index++) | 
|  | { | 
|  | const char *field = get_string_field( &context, index + 1 ); | 
|  | err = GetLastError(); | 
|  | if (field) | 
|  | { | 
|  | ok( err == 0, "line %u: bad error %u\n", i, err ); | 
|  | if (key_names[i].fields[index]) | 
|  | { | 
|  | if (i == 49) | 
|  | ok( !strcmp( field, key_names[i].fields[index] ) || | 
|  | !strcmp( field, A1200), /* Vista, W2K8 */ | 
|  | "line %u: bad field %s/%s\n", | 
|  | i, field, key_names[i].fields[index] ); | 
|  | else  /* don't compare drive letter of paths */ | 
|  | if (field[0] && field[1] == ':' && field[2] == '\\') | 
|  | ok( !strcmp( field + 1, key_names[i].fields[index] + 1 ), | 
|  | "line %u: bad field %s/%s\n", | 
|  | i, field, key_names[i].fields[index] ); | 
|  | else | 
|  | ok( !strcmp( field, key_names[i].fields[index] ), "line %u: bad field %s/%s\n", | 
|  | i, field, key_names[i].fields[index] ); | 
|  | } | 
|  | else | 
|  | ok( 0, "line %u: got extra field %s\n", i, field ); | 
|  | strcat( buffer, "," ); | 
|  | strcat( buffer, field ); | 
|  | } | 
|  | else | 
|  | { | 
|  | ok( err == 0 || err == ERROR_INVALID_PARAMETER, | 
|  | "line %u: bad error %u\n", i, err ); | 
|  | if (key_names[i].fields[index]) | 
|  | ok( 0, "line %u: missing field %s\n", i, key_names[i].fields[index] ); | 
|  | } | 
|  | if (!key_names[i].fields[index]) break; | 
|  | } | 
|  | count = SetupGetFieldCount( &context ); | 
|  | ok( count == index, "line %u: bad count %d/%d\n", i, index, count ); | 
|  |  | 
|  | line = get_line_text( &context ); | 
|  | ok( line != NULL, "line %u: SetupGetLineText failed\n", i ); | 
|  | if (line) ok( !strcmp( line, buffer+1 ), "line %u: bad text %s/%s\n", i, line, buffer+1 ); | 
|  |  | 
|  | SetupCloseInfFile( hinf ); | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | static void test_close_inf_file(void) | 
|  | { | 
|  | SetLastError(0xdeadbeef); | 
|  | SetupCloseInfFile(NULL); | 
|  | ok(GetLastError() == 0xdeadbeef || | 
|  | GetLastError() == ERROR_INVALID_PARAMETER, /* Win9x, WinMe */ | 
|  | "Expected 0xdeadbeef, got %u\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | SetupCloseInfFile(INVALID_HANDLE_VALUE); | 
|  | ok(GetLastError() == 0xdeadbeef || | 
|  | GetLastError() == ERROR_INVALID_PARAMETER, /* Win9x, WinMe */ | 
|  | "Expected 0xdeadbeef, got %u\n", GetLastError()); | 
|  | } | 
|  |  | 
|  | static const char *contents = "[Version]\n" | 
|  | "Signature=\"$Windows NT$\"\n" | 
|  | "FileVersion=5.1.1.2\n" | 
|  | "[FileBranchInfo]\n" | 
|  | "RTMQFE=\"%RTMGFE_NAME%\",SP1RTM,"A4097"\n" | 
|  | "[Strings]\n" | 
|  | "RTMQFE_NAME = \"RTMQFE\"\n"; | 
|  |  | 
|  | static const CHAR getfield_resA[][20] = | 
|  | { | 
|  | "RTMQFE", | 
|  | "%RTMGFE_NAME%", | 
|  | "SP1RTM", | 
|  | }; | 
|  |  | 
|  | static const WCHAR getfield_resW[][20] = | 
|  | { | 
|  | {'R','T','M','Q','F','E',0}, | 
|  | {'%','R','T','M','G','F','E','_','N','A','M','E','%',0}, | 
|  | {'S','P','1','R','T','M',0}, | 
|  | }; | 
|  |  | 
|  | static void test_pSetupGetField(void) | 
|  | { | 
|  | UINT err; | 
|  | BOOL ret; | 
|  | HINF hinf; | 
|  | LPCSTR fieldA; | 
|  | LPCWSTR fieldW; | 
|  | INFCONTEXT context; | 
|  | int i; | 
|  | int len; | 
|  | BOOL unicode = TRUE; | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | lstrcmpW(NULL, NULL); | 
|  | if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) | 
|  | { | 
|  | win_skip("Using A-functions instead of W\n"); | 
|  | unicode = FALSE; | 
|  | } | 
|  |  | 
|  | hinf = test_file_contents( contents, &err ); | 
|  | ok( hinf != NULL, "Expected valid INF file\n" ); | 
|  |  | 
|  | ret = SetupFindFirstLine( hinf, "FileBranchInfo", NULL, &context ); | 
|  | ok( ret, "Failed to find first line\n" ); | 
|  |  | 
|  | /* native Windows crashes if a NULL context is sent in */ | 
|  |  | 
|  | for ( i = 0; i < 3; i++ ) | 
|  | { | 
|  | if (unicode) | 
|  | { | 
|  | fieldW = pSetupGetFieldW( &context, i ); | 
|  | ok( fieldW != NULL, "Failed to get field %i\n", i ); | 
|  | ok( !lstrcmpW( getfield_resW[i], fieldW ), "Wrong string returned\n" ); | 
|  | } | 
|  | else | 
|  | { | 
|  | fieldA = pSetupGetFieldA( &context, i ); | 
|  | ok( fieldA != NULL, "Failed to get field %i\n", i ); | 
|  | ok( !lstrcmpA( getfield_resA[i], fieldA ), "Wrong string returned\n" ); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (unicode) | 
|  | { | 
|  | fieldW = pSetupGetFieldW( &context, 3 ); | 
|  | ok( fieldW != NULL, "Failed to get field 3\n" ); | 
|  | len = lstrlenW( fieldW ); | 
|  | ok( len == 511 || /* NT4, W2K, XP and W2K3 */ | 
|  | len == 4096,  /* Vista */ | 
|  | "Unexpected length, got %d\n", len ); | 
|  |  | 
|  | fieldW = pSetupGetFieldW( &context, 4 ); | 
|  | ok( fieldW == NULL, "Expected NULL, got %p\n", fieldW ); | 
|  | ok( GetLastError() == ERROR_INVALID_PARAMETER, | 
|  | "Expected ERROR_INVALID_PARAMETER, got %u\n", GetLastError() ); | 
|  | } | 
|  | else | 
|  | { | 
|  | fieldA = pSetupGetFieldA( &context, 3 ); | 
|  | ok( fieldA != NULL, "Failed to get field 3\n" ); | 
|  | len = lstrlenA( fieldA ); | 
|  | ok( len == 511, /* Win9x, WinME */ | 
|  | "Unexpected length, got %d\n", len ); | 
|  |  | 
|  | fieldA = pSetupGetFieldA( &context, 4 ); | 
|  | ok( fieldA == NULL, "Expected NULL, got %p\n", fieldA ); | 
|  | ok( GetLastError() == ERROR_INVALID_PARAMETER, | 
|  | "Expected ERROR_INVALID_PARAMETER, got %u\n", GetLastError() ); | 
|  | } | 
|  |  | 
|  | SetupCloseInfFile( hinf ); | 
|  | } | 
|  |  | 
|  | static void test_SetupGetIntField(void) | 
|  | { | 
|  | static const struct | 
|  | { | 
|  | const char *key; | 
|  | const char *fields; | 
|  | DWORD index; | 
|  | INT value; | 
|  | DWORD err; | 
|  | } keys[] = | 
|  | { | 
|  | /* key     fields            index   expected int  errorcode */ | 
|  | {  "Key", "48",             1,      48,           ERROR_SUCCESS }, | 
|  | {  "Key", "48",             0,      -1,           ERROR_INVALID_DATA }, | 
|  | {  "123", "48",             0,      123,          ERROR_SUCCESS }, | 
|  | {  "Key", "0x4",            1,      4,            ERROR_SUCCESS }, | 
|  | {  "Key", "Field1",         1,      -1,           ERROR_INVALID_DATA }, | 
|  | {  "Key", "Field1,34",      2,      34,           ERROR_SUCCESS }, | 
|  | {  "Key", "Field1,,Field3", 2,      0,            ERROR_SUCCESS }, | 
|  | {  "Key", "Field1,",        2,      0,            ERROR_SUCCESS } | 
|  | }; | 
|  | unsigned int i; | 
|  |  | 
|  | for (i = 0; i < sizeof(keys)/sizeof(keys[0]); i++) | 
|  | { | 
|  | HINF hinf; | 
|  | char buffer[MAX_INF_STRING_LENGTH]; | 
|  | INFCONTEXT context; | 
|  | UINT err; | 
|  | BOOL retb; | 
|  | INT intfield; | 
|  |  | 
|  | strcpy( buffer, STD_HEADER "[TestSection]\n" ); | 
|  | strcat( buffer, keys[i].key ); | 
|  | strcat( buffer, "=" ); | 
|  | strcat( buffer, keys[i].fields ); | 
|  | hinf = test_file_contents( buffer, &err); | 
|  | ok( hinf != NULL, "Expected valid INF file\n" ); | 
|  |  | 
|  | SetupFindFirstLineA( hinf, "TestSection", keys[i].key, &context ); | 
|  | SetLastError( 0xdeadbeef ); | 
|  | intfield = -1; | 
|  | retb = SetupGetIntField( &context, keys[i].index, &intfield ); | 
|  | if ( keys[i].err == ERROR_SUCCESS ) | 
|  | { | 
|  | ok( retb, "%u: Expected success\n", i ); | 
|  | ok( GetLastError() == ERROR_SUCCESS || | 
|  | GetLastError() == 0xdeadbeef /* win9x, NT4 */, | 
|  | "%u: Expected ERROR_SUCCESS or 0xdeadbeef, got %u\n", i, GetLastError() ); | 
|  | } | 
|  | else | 
|  | { | 
|  | ok( !retb, "%u: Expected failure\n", i ); | 
|  | ok( GetLastError() == keys[i].err, | 
|  | "%u: Expected %d, got %u\n", i, keys[i].err, GetLastError() ); | 
|  | } | 
|  | ok( intfield == keys[i].value, "%u: Expected %d, got %d\n", i, keys[i].value, intfield ); | 
|  |  | 
|  | SetupCloseInfFile( hinf ); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void test_GLE(void) | 
|  | { | 
|  | static const char *inf = | 
|  | "[Version]\n" | 
|  | "Signature=\"$Windows NT$\"\n" | 
|  | "[Sectionname]\n" | 
|  | "Keyname1=Field1,Field2,Field3\n" | 
|  | "\n" | 
|  | "Keyname2=Field4,Field5\n"; | 
|  | HINF hinf; | 
|  | UINT err; | 
|  | INFCONTEXT context; | 
|  | BOOL retb; | 
|  | LONG retl; | 
|  | char buf[MAX_INF_STRING_LENGTH]; | 
|  | int bufsize = MAX_INF_STRING_LENGTH; | 
|  | DWORD retsize; | 
|  |  | 
|  | hinf = test_file_contents( inf, &err ); | 
|  | ok( hinf != NULL, "Expected valid INF file\n" ); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retb = SetupFindFirstLineA( hinf, "ImNotThere", NULL, &context ); | 
|  | ok(!retb, "Expected failure\n"); | 
|  | ok(GetLastError() == ERROR_LINE_NOT_FOUND, | 
|  | "Expected ERROR_LINE_NOT_FOUND, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retb = SetupFindFirstLineA( hinf, "ImNotThere", "ImNotThere", &context ); | 
|  | ok(!retb, "Expected failure\n"); | 
|  | ok(GetLastError() == ERROR_LINE_NOT_FOUND, | 
|  | "Expected ERROR_LINE_NOT_FOUND, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retb = SetupFindFirstLineA( hinf, "Sectionname", NULL, &context ); | 
|  | ok(retb, "Expected success\n"); | 
|  | ok(GetLastError() == ERROR_SUCCESS, | 
|  | "Expected ERROR_SUCCESS, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retb = SetupFindFirstLineA( hinf, "Sectionname", "ImNotThere", &context ); | 
|  | ok(!retb, "Expected failure\n"); | 
|  | ok(GetLastError() == ERROR_LINE_NOT_FOUND, | 
|  | "Expected ERROR_LINE_NOT_FOUND, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retb = SetupFindFirstLineA( hinf, "Sectionname", "Keyname1", &context ); | 
|  | ok(retb, "Expected success\n"); | 
|  | ok(GetLastError() == ERROR_SUCCESS, | 
|  | "Expected ERROR_SUCCESS, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retb = SetupFindNextMatchLineA( &context, "ImNotThere", &context ); | 
|  | ok(!retb, "Expected failure\n"); | 
|  | ok(GetLastError() == ERROR_LINE_NOT_FOUND, | 
|  | "Expected ERROR_LINE_NOT_FOUND, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retb = SetupFindNextMatchLineA( &context, "Keyname2", &context ); | 
|  | ok(retb, "Expected success\n"); | 
|  | ok(GetLastError() == ERROR_SUCCESS, | 
|  | "Expected ERROR_SUCCESS, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retl = SetupGetLineCountA( hinf, "ImNotThere"); | 
|  | ok(retl == -1, "Expected -1, got %d\n", retl); | 
|  | ok(GetLastError() == ERROR_SECTION_NOT_FOUND, | 
|  | "Expected ERROR_SECTION_NOT_FOUND, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retl = SetupGetLineCountA( hinf, "Sectionname"); | 
|  | ok(retl == 2, "Expected 2, got %d\n", retl); | 
|  | ok(GetLastError() == ERROR_SUCCESS, | 
|  | "Expected ERROR_SUCCESS, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retb = SetupGetLineTextA( NULL, hinf, "ImNotThere", "ImNotThere", buf, bufsize, &retsize); | 
|  | ok(!retb, "Expected failure\n"); | 
|  | ok(GetLastError() == ERROR_LINE_NOT_FOUND, | 
|  | "Expected ERROR_LINE_NOT_FOUND, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retb = SetupGetLineTextA( NULL, hinf, "Sectionname", "ImNotThere", buf, bufsize, &retsize); | 
|  | ok(!retb, "Expected failure\n"); | 
|  | ok(GetLastError() == ERROR_LINE_NOT_FOUND, | 
|  | "Expected ERROR_LINE_NOT_FOUND, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retb = SetupGetLineTextA( NULL, hinf, "Sectionname", "Keyname1", buf, bufsize, &retsize); | 
|  | ok(retb, "Expected success\n"); | 
|  | ok(GetLastError() == ERROR_SUCCESS, | 
|  | "Expected ERROR_SUCCESS, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retb = SetupGetLineByIndexA( hinf, "ImNotThere", 1, &context ); | 
|  | ok(!retb, "Expected failure\n"); | 
|  | ok(GetLastError() == ERROR_LINE_NOT_FOUND, | 
|  | "Expected ERROR_LINE_NOT_FOUND, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retb = SetupGetLineByIndexA( hinf, "Sectionname", 1, &context ); | 
|  | ok(retb, "Expected success\n"); | 
|  | ok(GetLastError() == ERROR_SUCCESS, | 
|  | "Expected ERROR_SUCCESS, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetLastError(0xdeadbeef); | 
|  | retb = SetupGetLineByIndexA( hinf, "Sectionname", 3, &context ); | 
|  | ok(!retb, "Expected failure\n"); | 
|  | ok(GetLastError() == ERROR_LINE_NOT_FOUND, | 
|  | "Expected ERROR_LINE_NOT_FOUND, got %08x\n", GetLastError()); | 
|  |  | 
|  | SetupCloseInfFile( hinf ); | 
|  | } | 
|  |  | 
|  | START_TEST(parser) | 
|  | { | 
|  | init_function_pointers(); | 
|  | test_invalid_files(); | 
|  | test_section_names(); | 
|  | test_enum_sections(); | 
|  | test_key_names(); | 
|  | test_close_inf_file(); | 
|  | test_pSetupGetField(); | 
|  | test_SetupGetIntField(); | 
|  | test_GLE(); | 
|  | DeleteFileA( tmpfilename ); | 
|  | } |