shill: Support for profile identifiers, and creating persistent storage.

BUG=chromium-os:17252
TEST=unit tests

Change-Id: Iaec7b6b5737a997fde3d5215196fdcbf72eefe09
Reviewed-on: http://gerrit.chromium.org/gerrit/3749
Tested-by: Darin Petkov <petkov@chromium.org>
Reviewed-by: Chris Masone <cmasone@chromium.org>
diff --git a/Makefile b/Makefile
index 110ad26..463cff9 100644
--- a/Makefile
+++ b/Makefile
@@ -106,6 +106,7 @@
 	mock_control.o \
 	mock_device.o \
 	mock_service.o \
+	profile_unittest.o \
 	property_accessor_unittest.o \
 	property_store_unittest.o \
 	service_unittest.o \
diff --git a/default_profile.cc b/default_profile.cc
index 3f0da6d..74facf3 100644
--- a/default_profile.cc
+++ b/default_profile.cc
@@ -13,8 +13,9 @@
 namespace shill {
 
 DefaultProfile::DefaultProfile(ControlInterface *control_interface,
+                               GLib *glib,
                                const Manager::Properties &manager_props)
-    : Profile(control_interface) {
+    : Profile(control_interface, glib) {
   store_.RegisterConstString(flimflam::kCheckPortalListProperty,
                              &manager_props.check_portal_list);
   store_.RegisterConstString(flimflam::kCountryProperty,
diff --git a/default_profile.h b/default_profile.h
index db65487..746860c 100644
--- a/default_profile.h
+++ b/default_profile.h
@@ -23,6 +23,7 @@
 class DefaultProfile : public Profile {
  public:
   DefaultProfile(ControlInterface *control_interface,
+                 GLib *glib,
                  const Manager::Properties &manager_props);
   virtual ~DefaultProfile();
 
diff --git a/default_profile_unittest.cc b/default_profile_unittest.cc
index 6e3e712..8567143 100644
--- a/default_profile_unittest.cc
+++ b/default_profile_unittest.cc
@@ -23,12 +23,13 @@
 
 class DefaultProfileTest : public PropertyStoreTest {
  public:
-  DefaultProfileTest() : profile_(&control_interface_, properties_) {}
+  DefaultProfileTest() : profile_(&control_interface_, &glib_, properties_) {}
 
   virtual ~DefaultProfileTest() {}
 
  protected:
   DefaultProfile profile_;
+  GLib glib_;
   Manager::Properties properties_;
 };
 
diff --git a/profile.cc b/profile.cc
index 2593e56..c1e92a4 100644
--- a/profile.cc
+++ b/profile.cc
@@ -7,6 +7,7 @@
 #include <string>
 
 #include <base/logging.h>
+#include <base/string_util.h>
 #include <chromeos/dbus/service_constants.h>
 
 #include "shill/adaptor_interfaces.h"
@@ -16,8 +17,14 @@
 using std::string;
 
 namespace shill {
-Profile::Profile(ControlInterface *control_interface)
-    : adaptor_(control_interface->CreateProfileAdaptor(this)) {
+
+const char Profile::kGlobalStorageDir[] = "/var/cache/flimflam";
+const char Profile::kUserStorageDirFormat[] = "/home/%s/user/flimflam";
+
+Profile::Profile(ControlInterface *control_interface,
+                 GLib *glib)
+    : adaptor_(control_interface->CreateProfileAdaptor(this)),
+      storage_(glib) {
   // flimflam::kCheckPortalListProperty: Registered in DefaultProfile
   // flimflam::kCountryProperty: Registered in DefaultProfile
   store_.RegisterConstString(flimflam::kNameProperty, &name_);
@@ -36,4 +43,61 @@
 
 Profile::~Profile() {}
 
+bool Profile::IsValidIdentifierToken(const std::string &token) {
+  if (token.empty()) {
+    return false;
+  }
+  for (string::const_iterator it = token.begin(); it != token.end(); ++it) {
+    if (!IsAsciiAlpha(*it) && !IsAsciiDigit(*it)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Profile::ParseIdentifier(const string &raw, Identifier *parsed) {
+  if (raw.empty()) {
+    return false;
+  }
+  if (raw[0] == '~') {
+    // Format: "~user/identifier".
+    size_t slash = raw.find('/');
+    if (slash == string::npos) {
+      return false;
+    }
+    string user(raw.begin() + 1, raw.begin() + slash);
+    string identifier(raw.begin() + slash + 1, raw.end());
+    if (!IsValidIdentifierToken(user) || !IsValidIdentifierToken(identifier)) {
+      return false;
+    }
+    parsed->user = user;
+    parsed->identifier = identifier;
+    return true;
+  }
+
+  // Format: "identifier".
+  if (!IsValidIdentifierToken(raw)) {
+    return false;
+  }
+  parsed->user = "";
+  parsed->identifier = raw;
+  return true;
+}
+
+string Profile::GetRpcPath(const Identifier &identifier) {
+  string user = identifier.user.empty() ? "" : identifier.user + "/";
+  return "/profile/" + user + identifier.identifier;
+}
+
+bool Profile::GetStoragePath(const Identifier &identifier, FilePath *path) {
+  FilePath dir(
+      identifier.user.empty() ?
+      kGlobalStorageDir :
+      base::StringPrintf(kUserStorageDirFormat, identifier.user.c_str()));
+  // TODO(petkov): Validate the directory permissions, etc.
+  *path = dir.Append(base::StringPrintf("%s.profile",
+                                        identifier.identifier.c_str()));
+  return true;
+}
+
 }  // namespace shill
diff --git a/profile.h b/profile.h
index 11b9b80..9621d4a 100644
--- a/profile.h
+++ b/profile.h
@@ -9,24 +9,54 @@
 #include <vector>
 
 #include <base/memory/scoped_ptr.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
+#include "shill/key_file_store.h"
 #include "shill/property_store.h"
 #include "shill/refptr_types.h"
 #include "shill/shill_event.h"
 
+class FilePath;
+
 namespace shill {
 
 class ControlInterface;
 class Error;
+class GLib;
 class ProfileAdaptorInterface;
 
 class Profile {
  public:
-  explicit Profile(ControlInterface *control_interface);
+  struct Identifier {
+    std::string user;  // Empty for global.
+    std::string identifier;
+  };
+
+  static const char kGlobalStorageDir[];
+  static const char kUserStorageDirFormat[];
+
+  Profile(ControlInterface *control_interface, GLib *glib);
   virtual ~Profile();
 
   PropertyStore *store() { return &store_; }
 
+  // Parses a profile identifier. There're two acceptable forms of the |raw|
+  // identifier: "identifier" and "~user/identifier". Both "user" and
+  // "identifier" must be suitable for use in a D-Bus object path. Returns true
+  // on success.
+  static bool ParseIdentifier(const std::string &raw, Identifier *parsed);
+
+  // Returns the RPC object path for a profile identified by
+  // |identifier|. |identifier| must be a valid identifier, possibly parsed and
+  // validated through Profile::ParseIdentifier.
+  static std::string GetRpcPath(const Identifier &identifier);
+
+  // Sets |path| to the persistent store file path for a profile identified by
+  // |identifier|. Returns true on success, and false if unable to determine an
+  // appropriate file location. |identifier| must be a valid identifier,
+  // possibly parsed and validated through Profile::ParseIdentifier.
+  static bool GetStoragePath(const Identifier &identifier, FilePath *path);
+
  protected:
   // Properties to be get/set via PropertyStore calls that must also be visible
   // in subclasses.
@@ -34,9 +64,15 @@
 
  private:
   friend class ProfileAdaptorInterface;
+  FRIEND_TEST(ProfileTest, IsValidIdentifierToken);
+
+  static bool IsValidIdentifierToken(const std::string &token);
 
   scoped_ptr<ProfileAdaptorInterface> adaptor_;
 
+  // Persistent store associated with the profile.
+  KeyFileStore storage_;
+
   // Properties to be get/set via PropertyStore calls.
   std::string name_;
 
diff --git a/profile_unittest.cc b/profile_unittest.cc
new file mode 100644
index 0000000..d3a03a9
--- /dev/null
+++ b/profile_unittest.cc
@@ -0,0 +1,85 @@
+// 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 <base/string_util.h>
+#include <gtest/gtest.h>
+
+#include "shill/profile.h"
+
+using std::string;
+using testing::Test;
+
+namespace shill {
+
+class ProfileTest : public Test {
+};
+
+TEST_F(ProfileTest, IsValidIdentifierToken) {
+  EXPECT_FALSE(Profile::IsValidIdentifierToken(""));
+  EXPECT_FALSE(Profile::IsValidIdentifierToken(" "));
+  EXPECT_FALSE(Profile::IsValidIdentifierToken("-"));
+  EXPECT_FALSE(Profile::IsValidIdentifierToken("~"));
+  EXPECT_FALSE(Profile::IsValidIdentifierToken("_"));
+  EXPECT_TRUE(Profile::IsValidIdentifierToken("a"));
+  EXPECT_TRUE(Profile::IsValidIdentifierToken("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+  EXPECT_TRUE(Profile::IsValidIdentifierToken("abcdefghijklmnopqrstuvwxyz"));
+  EXPECT_TRUE(Profile::IsValidIdentifierToken("0123456789"));
+}
+
+TEST_F(ProfileTest, ParseIdentifier) {
+  Profile::Identifier identifier;
+  EXPECT_FALSE(Profile::ParseIdentifier("", &identifier));
+  EXPECT_FALSE(Profile::ParseIdentifier("~", &identifier));
+  EXPECT_FALSE(Profile::ParseIdentifier("~foo", &identifier));
+  EXPECT_FALSE(Profile::ParseIdentifier("~/", &identifier));
+  EXPECT_FALSE(Profile::ParseIdentifier("~bar/", &identifier));
+  EXPECT_FALSE(Profile::ParseIdentifier("~/zoo", &identifier));
+  EXPECT_FALSE(Profile::ParseIdentifier("~./moo", &identifier));
+  EXPECT_FALSE(Profile::ParseIdentifier("~valid/?", &identifier));
+  EXPECT_FALSE(Profile::ParseIdentifier("~no//no", &identifier));
+  EXPECT_FALSE(Profile::ParseIdentifier("~no~no", &identifier));
+
+  static const char kUser[] = "user";
+  static const char kIdentifier[] = "identifier";
+  EXPECT_TRUE(Profile::ParseIdentifier(
+      base::StringPrintf("~%s/%s", kUser, kIdentifier),
+      &identifier));
+  EXPECT_EQ(kUser, identifier.user);
+  EXPECT_EQ(kIdentifier, identifier.identifier);
+
+  EXPECT_FALSE(Profile::ParseIdentifier("!", &identifier));
+  EXPECT_FALSE(Profile::ParseIdentifier("/nope", &identifier));
+
+  static const char kIdentifier2[] = "something";
+  EXPECT_TRUE(Profile::ParseIdentifier(kIdentifier2, &identifier));
+  EXPECT_EQ("", identifier.user);
+  EXPECT_EQ(kIdentifier2, identifier.identifier);
+}
+
+TEST_F(ProfileTest, GetRpcPath) {
+  static const char kUser[] = "theUser";
+  static const char kIdentifier[] = "theIdentifier";
+  static const char kPathPrefix[] = "/profile/";
+  Profile::Identifier identifier;
+  identifier.identifier = kIdentifier;
+  EXPECT_EQ(string(kPathPrefix) + kIdentifier, Profile::GetRpcPath(identifier));
+  identifier.user = kUser;
+  EXPECT_EQ(string(kPathPrefix) + kUser + "/" + kIdentifier,
+            Profile::GetRpcPath(identifier));
+}
+
+TEST_F(ProfileTest, GetStoragePath) {
+  static const char kUser[] = "chronos";
+  static const char kIdentifier[] = "someprofile";
+  FilePath path;
+  Profile::Identifier identifier;
+  identifier.identifier = kIdentifier;
+  EXPECT_TRUE(Profile::GetStoragePath(identifier, &path));
+  EXPECT_EQ("/var/cache/flimflam/someprofile.profile", path.value());
+  identifier.user = kUser;
+  EXPECT_TRUE(Profile::GetStoragePath(identifier, &path));
+  EXPECT_EQ("/home/chronos/user/flimflam/someprofile.profile", path.value());
+}
+
+} // namespace shill