shill: OpenVPNDriver: Quote options containing spaces

When converting from command line arguments to config files,
the previous OpenVPN CL neglected to deal with arguments
containing spaces.  These need to be quoted.

BUG=chromium:269600
TEST=Unit tests

Change-Id: Id6aa5e8ed5eb9a97580d6ffe3bbb30992be5293a
Reviewed-on: https://gerrit.chromium.org/gerrit/64981
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Paul Stewart <pstew@chromium.org>
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index 60f67ee..c2af243 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -228,7 +228,23 @@
                                   char separator) {
   vector<string> option_strings;
   for (const auto &option : options) {
-    option_strings.push_back(JoinString(option, ' '));
+    vector<string> quoted_option;
+    for (const auto &argument : option) {
+      if (argument.find(' ') != string::npos ||
+          argument.find('\t') != string::npos ||
+          argument.find('"') != string::npos ||
+          argument.find(separator) != string::npos) {
+        string quoted_argument(argument);
+        const char separator_chars[] = { separator, '\0' };
+        ReplaceChars(argument, separator_chars, " ", &quoted_argument);
+        ReplaceChars(quoted_argument, "\\", "\\\\", &quoted_argument);
+        ReplaceChars(quoted_argument, "\"", "\\\"", &quoted_argument);
+        quoted_option.push_back("\"" + quoted_argument + "\"");
+      } else {
+        quoted_option.push_back(argument);
+      }
+    }
+    option_strings.push_back(JoinString(quoted_option, ' '));
   }
   return JoinString(option_strings, separator);
 }
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index 72bc923..e7a2929 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -1237,8 +1237,9 @@
   const char kOption1[] = "option1";
   const char kOption1Argument0[] = "option1-argument0";
   const char kOption2[] = "option2";
-  const char kOption2Argument0[] = "option2-argument0";
-  const char kOption2Argument1[] = "option2-argument1";
+  const char kOption2Argument0[] = "option2-argument0\n\t\"'\\";
+  const char kOption2Argument0Transformed[] = "option2-argument0 \t\\\"'\\\\";
+  const char kOption2Argument1[] = "option2-argument1 space";
   vector<vector<string>> options {
       { kOption0 },
       { kOption1, kOption1Argument0 },
@@ -1256,10 +1257,10 @@
   string config_contents;
   EXPECT_TRUE(file_util::ReadFileToString(config_file, &config_contents));
   string expected_config_contents = StringPrintf(
-      "%s\n%s %s\n%s %s %s\n",
+      "%s\n%s %s\n%s \"%s\" \"%s\"\n",
       kOption0,
       kOption1, kOption1Argument0,
-      kOption2, kOption2Argument0, kOption2Argument1);
+      kOption2, kOption2Argument0Transformed, kOption2Argument1);
   EXPECT_EQ(expected_config_contents, config_contents);
 }