libchromeos: Move data_encoding from Buffet to libchromeos

BUG=chromium:405714
TEST=USE=buffet ./build_packages

Change-Id: I9c9d3994d1e33a05cd5c978a2697164071ce5aa0
Reviewed-on: https://chromium-review.googlesource.com/213362
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Bertrand Simonnet <bsimonnet@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/chromeos/data_encoding.cc b/chromeos/data_encoding.cc
new file mode 100644
index 0000000..83e7a56
--- /dev/null
+++ b/chromeos/data_encoding.cc
@@ -0,0 +1,100 @@
+// Copyright 2014 The Chromium OS 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 <chromeos/data_encoding.h>
+
+#include <base/strings/stringprintf.h>
+#include <chromeos/string_utils.h>
+#include <cstring>
+
+namespace {
+
+inline int HexToDec(int hex) {
+  int dec = -1;
+  if (hex >= '0' && hex <= '9') {
+    dec = hex - '0';
+  } else if (hex >= 'A' && hex <= 'F') {
+    dec = hex - 'A' + 10;
+  } else if (hex >= 'a' && hex <= 'f') {
+    dec = hex - 'a' + 10;
+  }
+  return dec;
+}
+
+}  // namespace
+
+/////////////////////////////////////////////////////////////////////////
+namespace chromeos {
+namespace data_encoding {
+
+std::string UrlEncode(const char* data, bool encodeSpaceAsPlus) {
+  std::string result;
+
+  while (*data) {
+    char c = *data++;
+    // According to RFC3986 (http://www.faqs.org/rfcs/rfc3986.html),
+    // section 2.3. - Unreserved Characters
+    if ((c >= '0' && c <= '9') ||
+        (c >= 'A' && c <= 'Z') ||
+        (c >= 'a' && c <= 'z') ||
+        c == '-' || c == '.' || c == '_' || c == '~') {
+      result += c;
+    } else if (c == ' ' && encodeSpaceAsPlus) {
+      // For historical reasons, some URLs have spaces encoded as '+',
+      // this also applies to form data encoded as
+      // 'application/x-www-form-urlencoded'
+      result += '+';
+    } else {
+      base::StringAppendF(&result, "%%%02X",
+                          static_cast<unsigned char>(c));  // Encode as %NN
+    }
+  }
+  return result;
+}
+
+std::string UrlDecode(const char* data) {
+  std::string result;
+  while (*data) {
+    char c = *data++;
+    int part1 = 0, part2 = 0;
+    // HexToDec would return -1 even for character 0 (end of string),
+    // so it is safe to access data[0] and data[1] without overrunning the buf.
+    if (c == '%' &&
+        (part1 = HexToDec(data[0])) >= 0 && (part2 = HexToDec(data[1])) >= 0) {
+      c = static_cast<char>((part1 << 4) | part2);
+      data += 2;
+    } else if (c == '+') {
+      c = ' ';
+    }
+    result += c;
+  }
+  return result;
+}
+
+std::string WebParamsEncode(const WebParamList& params,
+                            bool encodeSpaceAsPlus) {
+  std::vector<std::string> pairs;
+  pairs.reserve(params.size());
+  for (const auto& p : params) {
+    std::string key = UrlEncode(p.first.c_str(), encodeSpaceAsPlus);
+    std::string value = UrlEncode(p.second.c_str(), encodeSpaceAsPlus);
+    pairs.push_back(chromeos::string_utils::Join('=', key, value));
+  }
+
+  return chromeos::string_utils::Join('&', pairs);
+}
+
+WebParamList WebParamsDecode(const std::string& data) {
+  WebParamList result;
+  std::vector<std::string> params = chromeos::string_utils::Split(data, '&');
+  for (const auto& p : params) {
+    auto pair = chromeos::string_utils::SplitAtFirst(p, '=');
+    result.emplace_back(UrlDecode(pair.first.c_str()),
+                        UrlDecode(pair.second.c_str()));
+  }
+  return result;
+}
+
+}  // namespace data_encoding
+}  // namespace chromeos
diff --git a/chromeos/data_encoding.h b/chromeos/data_encoding.h
new file mode 100644
index 0000000..1141ba8
--- /dev/null
+++ b/chromeos/data_encoding.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium OS 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 LIBCHROMEOS_CHROMEOS_DATA_ENCODING_H_
+#define LIBCHROMEOS_CHROMEOS_DATA_ENCODING_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace chromeos {
+namespace data_encoding {
+
+typedef std::vector<std::pair<std::string, std::string>> WebParamList;
+
+// Encode/escape string to be used in the query portion of a URL.
+// If |encodeSpaceAsPlus| is set to true, spaces are encoded as '+' instead
+// of "%20"
+std::string UrlEncode(const char* data, bool encodeSpaceAsPlus);
+
+inline std::string UrlEncode(const char* data) {
+  return UrlEncode(data, true);
+}
+
+// Decodes/unescapes a URL. Replaces all %XX sequences with actual characters.
+// Also replaces '+' with spaces.
+std::string UrlDecode(const char* data);
+
+// Converts a list of key-value pairs into a string compatible with
+// 'application/x-www-form-urlencoded' content encoding.
+std::string WebParamsEncode(const WebParamList& params, bool encodeSpaceAsPlus);
+
+inline std::string WebParamsEncode(const WebParamList& params) {
+  return WebParamsEncode(params, true);
+}
+
+// Parses a string of '&'-delimited key-value pairs (separated by '=') and
+// encoded in a way compatible with 'application/x-www-form-urlencoded'
+// content encoding.
+WebParamList WebParamsDecode(const std::string& data);
+
+}  // namespace data_encoding
+}  // namespace chromeos
+
+#endif  // LIBCHROMEOS_CHROMEOS_DATA_ENCODING_H_
diff --git a/chromeos/data_encoding_unittest.cc b/chromeos/data_encoding_unittest.cc
new file mode 100644
index 0000000..a09b09e
--- /dev/null
+++ b/chromeos/data_encoding_unittest.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 2011 The Chromium OS 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 <chromeos/data_encoding.h>
+
+#include <gtest/gtest.h>
+
+namespace chromeos {
+namespace data_encoding {
+
+TEST(data_encoding, UrlEncoding) {
+  std::string test = "\"http://sample/path/0014.html \"";
+  std::string encoded = UrlEncode(test.c_str());
+  EXPECT_EQ("%22http%3A%2F%2Fsample%2Fpath%2F0014.html+%22",
+            encoded);
+  EXPECT_EQ(test, UrlDecode(encoded.c_str()));
+
+  test = "\"http://sample/path/0014.html \"";
+  encoded = UrlEncode(test.c_str(), false);
+  EXPECT_EQ("%22http%3A%2F%2Fsample%2Fpath%2F0014.html%20%22",
+            encoded);
+  EXPECT_EQ(test, UrlDecode(encoded.c_str()));
+}
+
+TEST(data_encoding, WebParamsEncoding) {
+  std::string encoded = WebParamsEncode({{"q", "test"},
+                                         {"path", "/usr/bin"},
+                                         {"#", "%"}});
+  EXPECT_EQ("q=test&path=%2Fusr%2Fbin&%23=%25", encoded);
+
+  auto params = WebParamsDecode(encoded);
+  EXPECT_EQ(3, params.size());
+  EXPECT_EQ("q", params[0].first);
+  EXPECT_EQ("test", params[0].second);
+  EXPECT_EQ("path", params[1].first);
+  EXPECT_EQ("/usr/bin", params[1].second);
+  EXPECT_EQ("#", params[2].first);
+  EXPECT_EQ("%", params[2].second);
+}
+
+}  // namespace data_encoding
+}  // namespace chromeos
diff --git a/libchromeos.gypi b/libchromeos.gypi
index c28bc63..4a2f970 100644
--- a/libchromeos.gypi
+++ b/libchromeos.gypi
@@ -57,6 +57,7 @@
       'sources': [
         'chromeos/async_event_sequencer.cc',
         'chromeos/asynchronous_signal_handler.cc',
+        'chromeos/data_encoding.cc',
         'chromeos/dbus/abstract_dbus_service.cc',
         'chromeos/dbus/dbus.cc',
         'chromeos/dbus/dbus_object.cc',
@@ -204,6 +205,7 @@
           'sources': [
             'chromeos/async_event_sequencer_unittest.cc',
             'chromeos/asynchronous_signal_handler_unittest.cc',
+            'chromeos/data_encoding_unittest.cc',
             'chromeos/dbus/dbus_object_unittest.cc',
             'chromeos/error_unittest.cc',
             'chromeos/exported_object_manager_unittest.cc',