--- /dev/null
+////////////////////////////////////////////////////////////////////////////
+//
+// Copyright 2016 Realm Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////
+
+#include "sync/impl/sync_file.hpp"
+
+#include "util/time.hpp"
+
+#include <realm/util/file.hpp>
+#include <realm/util/scope_exit.hpp>
+
+#include <iomanip>
+#include <sstream>
+#include <system_error>
+#include <fstream>
+
+#ifdef _WIN32
+#include <io.h>
+#include <fcntl.h>
+
+inline static int mkstemp(char* _template) { return _open(_mktemp(_template), _O_CREAT | _O_TEMPORARY, _S_IREAD | _S_IWRITE); }
+#else
+#include <unistd.h>
+#endif
+
+
+using File = realm::util::File;
+
+namespace realm {
+
+namespace {
+
+uint8_t value_of_hex_digit(char hex_digit)
+{
+ if (hex_digit >= '0' && hex_digit <= '9') {
+ return hex_digit - '0';
+ } else if (hex_digit >= 'A' && hex_digit <= 'F') {
+ return 10 + hex_digit - 'A';
+ } else if (hex_digit >= 'a' && hex_digit <= 'f') {
+ return 10 + hex_digit - 'a';
+ } else {
+ throw std::invalid_argument("Cannot get the value of a character that isn't a hex digit.");
+ }
+}
+
+bool filename_is_reserved(const std::string& filename) {
+ return (filename == "." || filename == "..");
+}
+
+bool character_is_unreserved(char character)
+{
+ bool is_capital_letter = (character >= 'A' && character <= 'Z');
+ bool is_lowercase_letter = (character >= 'a' && character <= 'z');
+ bool is_number = (character >= '0' && character <= '9');
+ bool is_allowed_symbol = (character == '-' || character == '_' || character == '.');
+ return is_capital_letter || is_lowercase_letter || is_number || is_allowed_symbol;
+}
+
+char decoded_char_for(const std::string& percent_encoding, size_t index)
+{
+ if (index+2 >= percent_encoding.length()) {
+ throw std::invalid_argument("Malformed string: not enough characters after '%' before end of string.");
+ }
+ REALM_ASSERT(percent_encoding[index] == '%');
+ return (16*value_of_hex_digit(percent_encoding[index + 1])) + value_of_hex_digit(percent_encoding[index + 2]);
+}
+
+} // (anonymous namespace)
+
+namespace util {
+
+std::string make_percent_encoded_string(const std::string& raw_string)
+{
+ std::string buffer;
+ buffer.reserve(raw_string.size());
+ for (size_t i=0; i<raw_string.size(); i++) {
+ unsigned char character = raw_string[i];
+ if (character_is_unreserved(character)) {
+ buffer.push_back(character);
+ } else {
+ buffer.resize(buffer.size() + 3);
+ // Format string must resolve to exactly 3 characters.
+ sprintf(&buffer.back() - 2, "%%%2X", character);
+ }
+ }
+ return buffer;
+}
+
+std::string make_raw_string(const std::string& percent_encoded_string)
+{
+ std::string buffer;
+ size_t input_len = percent_encoded_string.length();
+ buffer.reserve(input_len);
+ size_t idx = 0;
+ while (idx < input_len) {
+ char current = percent_encoded_string[idx];
+ if (current == '%') {
+ // Decode. +3.
+ buffer.push_back(decoded_char_for(percent_encoded_string, idx));
+ idx += 3;
+ } else {
+ // No need to decode. +1.
+ if (!character_is_unreserved(current)) {
+ throw std::invalid_argument("Input string is invalid: contains reserved characters.");
+ }
+ buffer.push_back(current);
+ idx++;
+ }
+ }
+ return buffer;
+}
+
+std::string file_path_by_appending_component(const std::string& path, const std::string& component, FilePathType path_type)
+{
+ // FIXME: Does this have to be changed to accomodate Windows platforms?
+ std::string buffer;
+ buffer.reserve(2 + path.length() + component.length());
+ buffer.append(path);
+ std::string terminal = "";
+ if (path_type == FilePathType::Directory && component[component.length() - 1] != '/') {
+ terminal = "/";
+ }
+ char path_last = path[path.length() - 1];
+ char component_first = component[0];
+ if (path_last == '/' && component_first == '/') {
+ buffer.append(component.substr(1));
+ buffer.append(terminal);
+ } else if (path_last == '/' || component_first == '/') {
+ buffer.append(component);
+ buffer.append(terminal);
+ } else {
+ buffer.append("/");
+ buffer.append(component);
+ buffer.append(terminal);
+ }
+ return buffer;
+}
+
+std::string file_path_by_appending_extension(const std::string& path, const std::string& extension)
+{
+ std::string buffer;
+ buffer.reserve(1 + path.length() + extension.length());
+ buffer.append(path);
+ char path_last = path[path.length() - 1];
+ char extension_first = extension[0];
+ if (path_last == '.' && extension_first == '.') {
+ buffer.append(extension.substr(1));
+ } else if (path_last == '.' || extension_first == '.') {
+ buffer.append(extension);
+ } else {
+ buffer.append(".");
+ buffer.append(extension);
+ }
+ return buffer;
+}
+
+std::string create_timestamped_template(const std::string& prefix, int wildcard_count)
+{
+ constexpr int WILDCARD_MAX = 20;
+ constexpr int WILDCARD_MIN = 6;
+ wildcard_count = std::min(WILDCARD_MAX, std::max(WILDCARD_MIN, wildcard_count));
+ std::time_t time = std::time(nullptr);
+ std::stringstream stream;
+ stream << prefix << "-" << util::put_time(time, "%Y%m%d-%H%M%S") << "-" << std::string(wildcard_count, 'X');
+ return stream.str();
+}
+
+std::string reserve_unique_file_name(const std::string& path, const std::string& template_string)
+{
+ REALM_ASSERT_DEBUG(template_string.find("XXXXXX") != std::string::npos);
+ std::string path_buffer = file_path_by_appending_component(path, template_string, FilePathType::File);
+ int fd = mkstemp(&path_buffer[0]);
+ if (fd < 0) {
+ int err = errno;
+ throw std::system_error(err, std::system_category());
+ }
+ // Remove the file so we can use the name for our own file.
+#ifdef _WIN32
+ _close(fd);
+ _unlink(path_buffer.c_str());
+#else
+ close(fd);
+ unlink(path_buffer.c_str());
+#endif
+ return path_buffer;
+}
+
+} // util
+
+constexpr const char SyncFileManager::c_sync_directory[];
+constexpr const char SyncFileManager::c_utility_directory[];
+constexpr const char SyncFileManager::c_recovery_directory[];
+constexpr const char SyncFileManager::c_metadata_directory[];
+constexpr const char SyncFileManager::c_metadata_realm[];
+constexpr const char SyncFileManager::c_user_info_file[];
+
+std::string SyncFileManager::get_special_directory(std::string directory_name) const
+{
+ auto dir_path = file_path_by_appending_component(get_base_sync_directory(),
+ directory_name,
+ util::FilePathType::Directory);
+ util::try_make_dir(dir_path);
+ return dir_path;
+}
+
+std::string SyncFileManager::get_base_sync_directory() const
+{
+ auto sync_path = file_path_by_appending_component(m_base_path,
+ c_sync_directory,
+ util::FilePathType::Directory);
+ util::try_make_dir(sync_path);
+ return sync_path;
+}
+
+std::string SyncFileManager::user_directory(const std::string& local_identity,
+ util::Optional<SyncUserIdentifier> user_info) const
+{
+ REALM_ASSERT(local_identity.length() > 0);
+ std::string escaped = util::make_percent_encoded_string(local_identity);
+ if (filename_is_reserved(escaped))
+ throw std::invalid_argument("A user can't have an identifier reserved by the filesystem.");
+
+ auto user_path = file_path_by_appending_component(get_base_sync_directory(),
+ escaped,
+ util::FilePathType::Directory);
+ bool dir_created = util::try_make_dir(user_path);
+ if (dir_created && user_info) {
+ // Add a text file in the user directory containing the user identity, for backup purposes.
+ // Only do this the first time the directory is created.
+ auto info_path = util::file_path_by_appending_component(user_path, c_user_info_file);
+ std::ofstream info_file;
+ info_file.open(info_path.c_str());
+ if (info_file.is_open()) {
+ info_file << user_info->user_id << "\n" << user_info->auth_server_url << "\n";
+ info_file.close();
+ }
+ }
+ return user_path;
+}
+
+void SyncFileManager::remove_user_directory(const std::string& local_identity) const
+{
+ REALM_ASSERT(local_identity.length() > 0);
+ const auto& escaped = util::make_percent_encoded_string(local_identity);
+ if (filename_is_reserved(escaped))
+ throw std::invalid_argument("A user can't have an identifier reserved by the filesystem.");
+
+ auto user_path = file_path_by_appending_component(get_base_sync_directory(),
+ escaped,
+ util::FilePathType::Directory);
+ util::try_remove_dir_recursive(user_path);
+}
+
+bool SyncFileManager::try_rename_user_directory(const std::string& old_name, const std::string& new_name) const
+{
+ REALM_ASSERT_DEBUG(old_name.length() > 0 && new_name.length() > 0);
+ const auto& old_name_escaped = util::make_percent_encoded_string(old_name);
+ const auto& new_name_escaped = util::make_percent_encoded_string(new_name);
+ const std::string& base = get_base_sync_directory();
+ if (filename_is_reserved(old_name_escaped) || filename_is_reserved(new_name_escaped))
+ throw std::invalid_argument("A user directory can't be renamed using a reserved identifier.");
+
+ const auto& old_path = file_path_by_appending_component(base, old_name_escaped, util::FilePathType::Directory);
+ const auto& new_path = file_path_by_appending_component(base, new_name_escaped, util::FilePathType::Directory);
+
+ try {
+ File::move(old_path, new_path);
+ } catch (File::NotFound const&) {
+ return false;
+ }
+ return true;
+}
+
+bool SyncFileManager::remove_realm(const std::string& absolute_path) const
+{
+ REALM_ASSERT(absolute_path.length() > 0);
+ bool success = true;
+ // Remove the Realm file (e.g. "example.realm").
+ success = File::try_remove(absolute_path);
+ // Remove the lock file (e.g. "example.realm.lock").
+ auto lock_path = util::file_path_by_appending_extension(absolute_path, "lock");
+ success = File::try_remove(lock_path);
+ // Remove the management directory (e.g. "example.realm.management").
+ auto management_path = util::file_path_by_appending_extension(absolute_path, "management");
+ try {
+ util::try_remove_dir_recursive(management_path);
+ }
+ catch (File::AccessError const&) {
+ success = false;
+ }
+ return success;
+}
+
+bool SyncFileManager::copy_realm_file(const std::string& old_path, const std::string& new_path) const
+{
+ REALM_ASSERT(old_path.length() > 0);
+ try {
+ if (File::exists(new_path)) {
+ return false;
+ }
+ File::copy(old_path, new_path);
+ }
+ catch (File::NotFound const&) {
+ return false;
+ }
+ catch (File::AccessError const&) {
+ return false;
+ }
+ return true;
+}
+
+bool SyncFileManager::remove_realm(const std::string& local_identity, const std::string& raw_realm_path) const
+{
+ REALM_ASSERT(local_identity.length() > 0);
+ REALM_ASSERT(raw_realm_path.length() > 0);
+ if (filename_is_reserved(local_identity) || filename_is_reserved(raw_realm_path))
+ throw std::invalid_argument("A user or Realm can't have an identifier reserved by the filesystem.");
+
+ auto escaped = util::make_percent_encoded_string(raw_realm_path);
+ auto realm_path = util::file_path_by_appending_component(user_directory(local_identity), escaped);
+ return remove_realm(realm_path);
+}
+
+std::string SyncFileManager::path(const std::string& local_identity, const std::string& raw_realm_path,
+ util::Optional<SyncUserIdentifier> user_info) const
+{
+ REALM_ASSERT(local_identity.length() > 0);
+ REALM_ASSERT(raw_realm_path.length() > 0);
+ if (filename_is_reserved(local_identity) || filename_is_reserved(raw_realm_path))
+ throw std::invalid_argument("A user or Realm can't have an identifier reserved by the filesystem.");
+
+ auto escaped = util::make_percent_encoded_string(raw_realm_path);
+ auto realm_path = util::file_path_by_appending_component(user_directory(local_identity, user_info), escaped);
+ return realm_path;
+}
+
+std::string SyncFileManager::metadata_path() const
+{
+ auto dir_path = file_path_by_appending_component(get_utility_directory(),
+ c_metadata_directory,
+ util::FilePathType::Directory);
+ util::try_make_dir(dir_path);
+ return util::file_path_by_appending_component(dir_path, c_metadata_realm);
+}
+
+bool SyncFileManager::remove_metadata_realm() const
+{
+ auto dir_path = file_path_by_appending_component(get_utility_directory(),
+ c_metadata_directory,
+ util::FilePathType::Directory);
+ try {
+ util::try_remove_dir_recursive(dir_path);
+ return true;
+ }
+ catch (File::AccessError const&) {
+ return false;
+ }
+}
+
+} // realm