[2/6] msi: Rewrite command line parsing to handle quoted values correctly.

Hans Leidekker hans at codeweavers.com
Thu Dec 23 10:07:55 CST 2010


---
 dlls/msi/action.c        |  196 +++++++++++++++++++++++++++++++++-------------
 dlls/msi/tests/install.c |  173 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 316 insertions(+), 53 deletions(-)

diff --git a/dlls/msi/action.c b/dlls/msi/action.c
index 2cccdc9..48e77a3 100644
--- a/dlls/msi/action.c
+++ b/dlls/msi/action.c
@@ -299,79 +299,167 @@ static void ui_actioninfo(MSIPACKAGE *package, LPCWSTR action, BOOL start,
     msiobj_release(&row->hdr);
 }
 
+enum parse_state
+{
+    state_whitespace,
+    state_token,
+    state_quote
+};
+
+static int parse_prop( const WCHAR *str, WCHAR *value, int *quotes )
+{
+    enum parse_state state = state_quote;
+    const WCHAR *p;
+    WCHAR *out = value;
+    int ignore, in_quotes = 0, count = 0, len = 0;
+
+    for (p = str; *p; p++)
+    {
+        ignore = 0;
+        switch (state)
+        {
+        case state_whitespace:
+            switch (*p)
+            {
+            case ' ':
+                if (!count) goto done;
+                in_quotes = 1;
+                ignore = 1;
+                break;
+            case '"':
+                state = state_quote;
+                if (in_quotes) count--;
+                else count++;
+                break;
+            default:
+                state = state_token;
+                if (!count) in_quotes = 0;
+                else in_quotes = 1;
+                len++;
+                break;
+            }
+            break;
+
+        case state_token:
+            switch (*p)
+            {
+            case '"':
+                state = state_quote;
+                if (in_quotes) count--;
+                else count++;
+                break;
+            case ' ':
+                state = state_whitespace;
+                if (!count) goto done;
+                in_quotes = 1;
+                break;
+            default:
+                if (!count) in_quotes = 0;
+                else in_quotes = 1;
+                len++;
+                break;
+            }
+            break;
+
+        case state_quote:
+            switch (*p)
+            {
+            case '"':
+                if (in_quotes) count--;
+                else count++;
+                break;
+            case ' ':
+                state = state_whitespace;
+                if (!count || !len) goto done;
+                in_quotes = 1;
+                break;
+            default:
+                state = state_token;
+                if (!count) in_quotes = 0;
+                else in_quotes = 1;
+                len++;
+                break;
+            }
+            break;
+
+        default: break;
+        }
+        if (!ignore) *out++ = *p;
+    }
+
+done:
+    if (!len) *value = 0;
+    else *out = 0;
+
+    *quotes = count;
+    return p - str;
+}
+
+static void remove_quotes( WCHAR *str )
+{
+    WCHAR *p = str;
+    int len = strlenW( str );
+
+    while ((p = strchrW( p, '"' )))
+    {
+        memmove( p, p + 1, (len - (p - str)) * sizeof(WCHAR) );
+        p++;
+    }
+}
+
 UINT msi_parse_command_line( MSIPACKAGE *package, LPCWSTR szCommandLine,
                              BOOL preserve_case )
 {
-    LPCWSTR ptr,ptr2;
-    BOOL quote;
+    LPCWSTR ptr, ptr2;
+    int quotes;
     DWORD len;
-    LPWSTR prop = NULL, val = NULL;
+    WCHAR *prop, *val;
+    UINT r;
 
     if (!szCommandLine)
         return ERROR_SUCCESS;
 
     ptr = szCommandLine;
-       
     while (*ptr)
     {
-        if (*ptr==' ')
-        {
-            ptr++;
-            continue;
-        }
+        while (*ptr == ' ') ptr++;
+        if (!*ptr) break;
 
-        TRACE("Looking at %s\n",debugstr_w(ptr));
-
-        ptr2 = strchrW(ptr,'=');
-        if (!ptr2)
-        {
-            ERR("command line contains unknown string : %s\n", debugstr_w(ptr));
-            break;
-        }
+        ptr2 = strchrW( ptr, '=' );
+        if (!ptr2) return ERROR_INVALID_COMMAND_LINE;
  
-        quote = FALSE;
+        len = ptr2 - ptr;
+        if (!len) return ERROR_INVALID_COMMAND_LINE;
 
-        len = ptr2-ptr;
-        prop = msi_alloc((len+1)*sizeof(WCHAR));
-        memcpy(prop,ptr,len*sizeof(WCHAR));
-        prop[len]=0;
-
-        if (!preserve_case)
-            struprW(prop);
+        prop = msi_alloc( (len + 1) * sizeof(WCHAR) );
+        memcpy( prop, ptr, len * sizeof(WCHAR) );
+        prop[len] = 0;
+        if (!preserve_case) struprW( prop );
 
         ptr2++;
-       
-        len = 0; 
-        ptr = ptr2; 
-        while (*ptr && (quote || (!quote && *ptr!=' ')))
-        {
-            if (*ptr == '"')
-                quote = !quote;
-            ptr++;
-            len++;
-        }
-       
-        if (*ptr2=='"')
+        while (*ptr2 == ' ') ptr2++;
+
+        quotes = 0;
+        val = msi_alloc( (strlenW( ptr2 ) + 1) * sizeof(WCHAR) );
+        len = parse_prop( ptr2, val, &quotes );
+        if (quotes % 2)
         {
-            ptr2++;
-            len -= 2;
+            WARN("unbalanced quotes\n");
+            msi_free( val );
+            msi_free( prop );
+            return ERROR_INVALID_COMMAND_LINE;
         }
-        val = msi_alloc((len+1)*sizeof(WCHAR));
-        memcpy(val,ptr2,len*sizeof(WCHAR));
-        val[len] = 0;
+        remove_quotes( val );
+        TRACE("Found commandline property %s = %s\n", debugstr_w(prop), debugstr_w(val));
 
-        if (lstrlenW(prop) > 0)
-        {
-            UINT r = msi_set_property( package->db, prop, val );
+        r = msi_set_property( package->db, prop, val );
+        if (r == ERROR_SUCCESS && !strcmpW( prop, cszSourceDir ))
+            msi_reset_folders( package, TRUE );
 
-            TRACE("Found commandline property (%s) = (%s)\n", 
-                   debugstr_w(prop), debugstr_w(val));
+        msi_free( val );
+        msi_free( prop );
 
-            if (r == ERROR_SUCCESS && !strcmpW( prop, cszSourceDir ))
-                msi_reset_folders( package, TRUE );
-        }
-        msi_free(val);
-        msi_free(prop);
+        ptr = ptr2 + len;
     }
 
     return ERROR_SUCCESS;
@@ -7356,7 +7444,9 @@ UINT MSI_InstallPackage( MSIPACKAGE *package, LPCWSTR szPackagePath,
         msi_set_sourcedir_props(package, FALSE);
     }
 
-    msi_parse_command_line( package, szCommandLine, FALSE );
+    rc = msi_parse_command_line( package, szCommandLine, FALSE );
+    if (rc != ERROR_SUCCESS)
+        return rc;
 
     msi_apply_transforms( package );
     msi_apply_patches( package );
diff --git a/dlls/msi/tests/install.c b/dlls/msi/tests/install.c
index 211d6a8..dfcf178 100644
--- a/dlls/msi/tests/install.c
+++ b/dlls/msi/tests/install.c
@@ -1169,6 +1169,25 @@ static const CHAR sd_custom_action_dat[] = "Action\tType\tSource\tTarget\tISComm
                                            "TestSourceDirProp34\t19\t\tTest 34 failed\t\n"
                                            "TestSourceDirProp35\t19\t\tTest 35 failed\t\n";
 
+static const CHAR cl_custom_action_dat[] = "Action\tType\tSource\tTarget\tISComments\n"
+                                           "s72\ti2\tS64\tS0\tS255\n"
+                                           "CustomAction\tAction\n"
+                                           "TestCommandlineProp\t19\t\tTest1\t\n";
+
+static const CHAR cl_install_exec_seq_dat[] = "Action\tCondition\tSequence\n"
+                                              "s72\tS255\tI2\n"
+                                              "InstallExecuteSequence\tAction\n"
+                                              "LaunchConditions\t\t100\n"
+                                              "ValidateProductID\t\t700\n"
+                                              "CostInitialize\t\t800\n"
+                                              "FileCost\t\t900\n"
+                                              "CostFinalize\t\t1000\n"
+                                              "TestCommandlineProp\tP=\"one\"\t1100\n"
+                                              "InstallInitialize\t\t1500\n"
+                                              "ProcessComponents\t\t1600\n"
+                                              "InstallValidate\t\t1400\n"
+                                              "InstallFinalize\t\t5000\n";
+
 typedef struct _msi_table
 {
     const CHAR *filename;
@@ -1835,6 +1854,19 @@ static const msi_table pv_tables[] =
     ADD_TABLE(property)
 };
 
+static const msi_table cl_tables[] =
+{
+    ADD_TABLE(component),
+    ADD_TABLE(directory),
+    ADD_TABLE(feature),
+    ADD_TABLE(feature_comp),
+    ADD_TABLE(file),
+    ADD_TABLE(cl_custom_action),
+    ADD_TABLE(cl_install_exec_seq),
+    ADD_TABLE(media),
+    ADD_TABLE(property)
+};
+
 /* cabinet definitions */
 
 /* make the max size large so there is only one cab file */
@@ -6168,6 +6200,146 @@ error:
     RemoveDirectory("msitest");
 }
 
+static void test_command_line_parsing(void)
+{
+    UINT r;
+    const char *cmd; 
+
+    if (is_process_limited())
+    {
+        skip("process is limited\n");
+        return;
+    }
+
+    create_test_files();
+    create_database(msifile, cl_tables, sizeof(cl_tables)/sizeof(msi_table));
+
+    MsiSetInternalUI(INSTALLUILEVEL_NONE, NULL);
+
+    cmd = " ";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    cmd = "=";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INVALID_COMMAND_LINE, "Expected ERROR_INVALID_COMMAND_LINE, got %u\n", r);
+
+    cmd = "==";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INVALID_COMMAND_LINE, "Expected ERROR_INVALID_COMMAND_LINE, got %u\n", r);
+
+    cmd = "one";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INVALID_COMMAND_LINE, "Expected ERROR_INVALID_COMMAND_LINE, got %u\n", r);
+
+    cmd = "=one";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INVALID_COMMAND_LINE, "Expected ERROR_INVALID_COMMAND_LINE, got %u\n", r);
+
+    cmd = "P=";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    cmd = "  P=";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    cmd = "P=  ";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    cmd = "P=\"";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INVALID_COMMAND_LINE, "Expected ERROR_INVALID_COMMAND_LINE, got %u\n", r);
+
+    cmd = "P=\"\"";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    cmd = "P=\"\"\"";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INVALID_COMMAND_LINE, "Expected ERROR_INVALID_COMMAND_LINE, got %u\n", r);
+
+    cmd = "P=\"\"\"\"";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    cmd = "P=\" ";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INVALID_COMMAND_LINE, "Expected ERROR_INVALID_COMMAND_LINE, got %u\n", r);
+
+    cmd = "P= \"";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INVALID_COMMAND_LINE, "Expected ERROR_INVALID_COMMAND_LINE, got %u\n", r);
+
+    cmd = "P= \"\" ";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    cmd = "P=one";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INSTALL_FAILURE, "Expected ERROR_INSTALL_FAILURE, got %u\n", r);
+
+    cmd = "P= one";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INSTALL_FAILURE, "Expected ERROR_INSTALL_FAILURE, got %u\n", r);
+
+    cmd = "P=\"one";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INVALID_COMMAND_LINE, "Expected ERROR_INVALID_COMMAND_LINE, got %u\n", r);
+
+    cmd = "P=one\"";
+    r = MsiInstallProductA(msifile, cmd);
+    todo_wine ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    cmd = "P=\"one\"";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INSTALL_FAILURE, "Expected ERROR_INSTALL_FAILURE, got %u\n", r);
+
+    cmd = "P= \"one\" ";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INSTALL_FAILURE, "Expected ERROR_INSTALL_FAILURE, got %u\n", r);
+
+    cmd = "P=\"one\"\"";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INVALID_COMMAND_LINE, "Expected ERROR_INVALID_COMMAND_LINE, got %u\n", r);
+
+    cmd = "P=\"\"one\"";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INVALID_COMMAND_LINE, "Expected ERROR_INVALID_COMMAND_LINE, got %u\n", r);
+
+    cmd = "P=\"\"one\"\"";
+    r = MsiInstallProductA(msifile, cmd);
+    todo_wine ok(r == ERROR_INVALID_COMMAND_LINE, "Expected ERROR_INVALID_COMMAND_LINE, got %u\n", r);
+
+    cmd = "P=\"one two\"";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    cmd = "P=\"\"\"one\"\" two\"";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    cmd = "P=\"\"\"one\"\" two\" Q=three";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    cmd = "P=\"\" Q=\"two\"";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    cmd = "P=\"one\" Q=\"two\"";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_INSTALL_FAILURE, "Expected ERROR_INSTALL_FAILURE, got %u\n", r);
+
+    cmd = "P=\"one=two\"";
+    r = MsiInstallProductA(msifile, cmd);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    DeleteFile(msifile);
+    RemoveDirectory("msitest");
+}
+
 START_TEST(install)
 {
     DWORD len;
@@ -6257,6 +6429,7 @@ START_TEST(install)
     test_icon_table();
     test_sourcedir_props();
     test_package_validation();
+    test_command_line_parsing();
 
     DeleteFileA(log_file);
 
-- 
1.7.1







More information about the wine-patches mailing list