Move two generic string split functions from sync API to their own API in base/string_split.

BUG=None 
TEST=base_unittests

Original patch by Thiago Farina
Original Review: http://codereview.chromium.org/464075

Review URL: http://codereview.chromium.org/502074

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@36774 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/base/base.gyp b/base/base.gyp
index 3726622..d59ebc14 100644
--- a/base/base.gyp
+++ b/base/base.gyp
@@ -116,6 +116,7 @@
         'stack_container_unittest.cc',
         'stats_table_unittest.cc',
         'string_piece_unittest.cc',
+        'string_split_unittest.cc',
         'string_tokenizer_unittest.cc',
         'string_util_unittest.cc',
         'sys_info_unittest.cc',
diff --git a/base/base.gypi b/base/base.gypi
index ecdc0a0b..47789de 100644
--- a/base/base.gypi
+++ b/base/base.gypi
@@ -197,6 +197,8 @@
           'stl_util-inl.h',
           'string_piece.cc',
           'string_piece.h',
+          'string_split.cc',
+          'string_split.h',
           'string_tokenizer.h',
           'string_util.cc',
           'string_util.h',
diff --git a/base/string_split.cc b/base/string_split.cc
new file mode 100644
index 0000000..4494d25
--- /dev/null
+++ b/base/string_split.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/string_split.h"
+
+#include "base/string_util.h"
+
+namespace base {
+
+bool SplitStringIntoKeyValues(
+    const std::string& line,
+    char key_value_delimiter,
+    std::string* key, std::vector<std::string>* values) {
+  key->clear();
+  values->clear();
+
+  // find the key string
+  size_t end_key_pos = line.find_first_of(key_value_delimiter);
+  if (end_key_pos == std::string::npos) {
+    DLOG(INFO) << "cannot parse key from line: " << line;
+    return false;    // no key
+  }
+  key->assign(line, 0, end_key_pos);
+
+  // find the values string
+  std::string remains(line, end_key_pos, line.size() - end_key_pos);
+  size_t begin_values_pos = remains.find_first_not_of(key_value_delimiter);
+  if (begin_values_pos == std::string::npos) {
+    DLOG(INFO) << "cannot parse value from line: " << line;
+    return false;   // no value
+  }
+  std::string values_string(remains, begin_values_pos,
+                            remains.size() - begin_values_pos);
+
+  // construct the values vector
+  values->push_back(values_string);
+  return true;
+}
+
+bool SplitStringIntoKeyValuePairs(
+    const std::string& line,
+    char key_value_delimiter,
+    char key_value_pair_delimiter,
+    std::vector<std::pair<std::string, std::string> >* kv_pairs) {
+  kv_pairs->clear();
+
+  std::vector<std::string> pairs;
+  SplitString(line, key_value_pair_delimiter, &pairs);
+
+  bool success = true;
+  for (size_t i = 0; i < pairs.size(); ++i) {
+    std::string key;
+    std::vector<std::string> value;
+    if (!SplitStringIntoKeyValues(pairs[i],
+                                  key_value_delimiter,
+                                  &key, &value)) {
+      // Don't return here, to allow for keys without associated
+      // values; just record that our split failed.
+      success = false;
+    }
+    DCHECK_LE(value.size(), 1U);
+    kv_pairs->push_back(make_pair(key, value.empty()? "" : value[0]));
+  }
+  return success;
+}
+
+}  // namespace base
diff --git a/base/string_split.h b/base/string_split.h
new file mode 100644
index 0000000..3e7881f
--- /dev/null
+++ b/base/string_split.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRING_SPLIT_H_
+#define BASE_STRING_SPLIT_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace base {
+
+bool SplitStringIntoKeyValues(
+    const std::string& line,
+    char key_value_delimiter,
+    std::string* key, std::vector<std::string>* values);
+
+bool SplitStringIntoKeyValuePairs(
+    const std::string& line,
+    char key_value_delimiter,
+    char key_value_pair_delimiter,
+    std::vector<std::pair<std::string, std::string> >* kv_pairs);
+
+}  // namespace base
+
+#endif  // BASE_STRING_SPLIT_H
diff --git a/base/string_split_unittest.cc b/base/string_split_unittest.cc
new file mode 100644
index 0000000..984e6e8
--- /dev/null
+++ b/base/string_split_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/string_split.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+class SplitStringIntoKeyValuesTest : public testing::Test {
+ protected:
+  std::string key;
+  std::vector<std::string> values;
+};
+
+TEST_F(SplitStringIntoKeyValuesTest, EmptyInputMultipleValues) {
+  EXPECT_FALSE(SplitStringIntoKeyValues("",     // Empty input
+                                        '\t',   // Key separators
+                                        &key, &values));
+  EXPECT_TRUE(key.empty());
+  EXPECT_TRUE(values.empty());
+}
+
+TEST_F(SplitStringIntoKeyValuesTest, EmptyValueInputMultipleValues) {
+  EXPECT_FALSE(SplitStringIntoKeyValues("key_with_no_value\t",
+                                        '\t',  // Key separators
+                                        &key, &values));
+  EXPECT_EQ("key_with_no_value", key);
+  EXPECT_TRUE(values.empty());
+}
+
+TEST_F(SplitStringIntoKeyValuesTest, EmptyKeyInputMultipleValues) {
+  EXPECT_TRUE(SplitStringIntoKeyValues("\tvalue for empty key",
+                                       '\t',  // Key separators
+                                       &key, &values));
+  EXPECT_TRUE(key.empty());
+  ASSERT_EQ(1U, values.size());
+}
+
+TEST_F(SplitStringIntoKeyValuesTest, KeyWithMultipleValues) {
+  EXPECT_TRUE(SplitStringIntoKeyValues("key1\tvalue1,   value2   value3",
+                                       '\t',  // Key separators
+                                       &key, &values));
+  EXPECT_EQ("key1", key);
+  ASSERT_EQ(1U, values.size());
+  EXPECT_EQ("value1,   value2   value3", values[0]);
+}
+
+TEST_F(SplitStringIntoKeyValuesTest, EmptyInputSingleValue) {
+  EXPECT_FALSE(SplitStringIntoKeyValues("",     // Empty input
+                                        '\t',   // Key separators
+                                        &key, &values));
+  EXPECT_TRUE(key.empty());
+  EXPECT_TRUE(values.empty());
+}
+
+TEST_F(SplitStringIntoKeyValuesTest, EmptyValueInputSingleValue) {
+  EXPECT_FALSE(SplitStringIntoKeyValues("key_with_no_value\t",
+                                        '\t',  // Key separators
+                                        &key, &values));
+  EXPECT_EQ("key_with_no_value", key);
+  EXPECT_TRUE(values.empty());
+}
+
+TEST_F(SplitStringIntoKeyValuesTest, EmptyKeyInputSingleValue) {
+  EXPECT_TRUE(SplitStringIntoKeyValues("\tvalue for empty key",
+                                       '\t',  // Key separators
+                                       &key, &values));
+  EXPECT_TRUE(key.empty());
+  ASSERT_EQ(1U, values.size());
+  EXPECT_EQ("value for empty key", values[0]);
+}
+
+TEST_F(SplitStringIntoKeyValuesTest, KeyWithSingleValue) {
+  EXPECT_TRUE(SplitStringIntoKeyValues("key1\tvalue1,   value2   value3",
+                                       '\t',  // Key separators
+                                       &key, &values));
+  EXPECT_EQ("key1", key);
+  ASSERT_EQ(1U, values.size());
+  EXPECT_EQ("value1,   value2   value3", values[0]);
+}
+
+class SplitStringIntoKeyValuePairsTest : public testing::Test {
+ protected:
+  std::vector<std::pair<std::string, std::string> > kv_pairs;
+};
+
+TEST_F(SplitStringIntoKeyValuePairsTest, DISABLED_EmptyString) {
+  EXPECT_TRUE(SplitStringIntoKeyValuePairs("",
+                                           ':',   // Key-value delimiters
+                                           ',',   // Key-value pair delims
+                                           &kv_pairs));
+  EXPECT_TRUE(kv_pairs.empty());
+}
+
+TEST_F(SplitStringIntoKeyValuePairsTest, EmptySecondValue) {
+  EXPECT_FALSE(SplitStringIntoKeyValuePairs("key1:value1 , key2:",
+                                            ':',   // Key-value delimiters
+                                            ',',   // Key-value pair delims
+                                            &kv_pairs));
+  ASSERT_EQ(2U, kv_pairs.size());
+  EXPECT_EQ("key1", kv_pairs[0].first);
+  EXPECT_EQ("value1", kv_pairs[0].second);
+  EXPECT_EQ("key2", kv_pairs[1].first);
+  EXPECT_EQ("", kv_pairs[1].second);
+}
+
+TEST_F(SplitStringIntoKeyValuePairsTest, DelimiterInValue) {
+  EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1:va:ue1 , key2:value2",
+                                           ':',   // Key-value delimiters
+                                           ',',   // Key-value pair delims
+                                           &kv_pairs));
+  ASSERT_EQ(2U, kv_pairs.size());
+  EXPECT_EQ("key1", kv_pairs[0].first);
+  EXPECT_EQ("va:ue1", kv_pairs[0].second);
+  EXPECT_EQ("key2", kv_pairs[1].first);
+  EXPECT_EQ("value2", kv_pairs[1].second);
+}
+
+}  // namespace base
diff --git a/base/string_util.h b/base/string_util.h
index c895f27..34f9386 100644
--- a/base/string_util.h
+++ b/base/string_util.h
@@ -530,6 +530,8 @@
   }
 };
 
+// TODO(timsteele): Move these split string functions into their own API on
+// string_split.cc/.h files.
 //-----------------------------------------------------------------------------
 
 // Splits |str| into a vector of strings delimited by |s|. Append the results
diff --git a/chrome/browser/sync/engine/net/gaia_authenticator.cc b/chrome/browser/sync/engine/net/gaia_authenticator.cc
index 3dfaae4..da263a9 100644
--- a/chrome/browser/sync/engine/net/gaia_authenticator.cc
+++ b/chrome/browser/sync/engine/net/gaia_authenticator.cc
@@ -10,7 +10,7 @@
 
 #include "base/basictypes.h"
 #include "base/port.h"
-#include "base/string_util.h"
+#include "base/string_split.h"
 #include "chrome/browser/sync/engine/all_status.h"
 #include "chrome/browser/sync/engine/net/http_return.h"
 #include "chrome/browser/sync/engine/net/url_translator.h"
@@ -21,63 +21,6 @@
 using std::string;
 using std::vector;
 
-// TODO(timsteele): Integrate the following two functions to string_util.h or
-// somewhere that makes them unit-testable.
-bool SplitStringIntoKeyValues(const string& line,
-                              char key_value_delimiter,
-                              string* key, vector<string>* values) {
-  key->clear();
-  values->clear();
-
-  // find the key string
-  size_t end_key_pos = line.find_first_of(key_value_delimiter);
-  if (end_key_pos == string::npos) {
-    DLOG(INFO) << "cannot parse key from line: " << line;
-    return false;    // no key
-  }
-  key->assign(line, 0, end_key_pos);
-
-  // find the values string
-  string remains(line, end_key_pos, line.size() - end_key_pos);
-  size_t begin_values_pos = remains.find_first_not_of(key_value_delimiter);
-  if (begin_values_pos == string::npos) {
-    DLOG(INFO) << "cannot parse value from line: " << line;
-    return false;   // no value
-  }
-  string values_string(remains, begin_values_pos,
-                       remains.size() - begin_values_pos);
-
-  // construct the values vector
-  values->push_back(values_string);
-  return true;
-}
-
-bool SplitStringIntoKeyValuePairs(const string& line,
-                                  char key_value_delimiter,
-                                  char key_value_pair_delimiter,
-                                  vector<pair<string, string> >* kv_pairs) {
-  kv_pairs->clear();
-
-  vector<string> pairs;
-  SplitString(line, key_value_pair_delimiter, &pairs);
-
-  bool success = true;
-  for (size_t i = 0; i < pairs.size(); ++i) {
-    string key;
-    vector<string> value;
-    if (!SplitStringIntoKeyValues(pairs[i],
-                                  key_value_delimiter,
-                                  &key, &value)) {
-      // Don't return here, to allow for keys without associated
-      // values; just record that our split failed.
-      success = false;
-    }
-    DCHECK_LE(value.size(), 1U);
-    kv_pairs->push_back(make_pair(key, value.empty()? "" : value[0]));
-  }
-  return success;
-}
-
 namespace browser_sync {
 
 static const char kGaiaV1IssueAuthTokenPath[] = "/accounts/IssueAuthToken";
@@ -288,7 +231,7 @@
   } else if (RC_REQUEST_OK == server_response_code) {
     typedef vector<pair<string, string> > Tokens;
     Tokens tokens;
-    SplitStringIntoKeyValuePairs(message_text, '=', '\n', &tokens);
+    base::SplitStringIntoKeyValuePairs(message_text, '=', '\n', &tokens);
     for (Tokens::iterator i = tokens.begin(); i != tokens.end(); ++i) {
       if ("accountType" == i->first) {
         // We never authenticate an email as a hosted account.
@@ -356,7 +299,7 @@
 void GaiaAuthenticator::ExtractTokensFrom(const string& response,
                                           AuthResults* results) {
   vector<pair<string, string> > tokens;
-  SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens);
+  base::SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens);
   for (vector<pair<string, string> >::iterator i = tokens.begin();
       i != tokens.end(); ++i) {
     if (i->first == "SID") {
@@ -374,7 +317,7 @@
 void GaiaAuthenticator::ExtractAuthErrorFrom(const string& response,
                                              AuthResults* results) {
   vector<pair<string, string> > tokens;
-  SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens);
+  base::SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens);
   for (vector<pair<string, string> >::iterator i = tokens.begin();
       i != tokens.end(); ++i) {
     if (i->first == "Error") {