1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2016 Realm Inc.
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
17 ////////////////////////////////////////////////////////////////////////////
19 #include "sync/impl/sync_file.hpp"
21 #include "util/time.hpp"
23 #include <realm/util/file.hpp>
24 #include <realm/util/scope_exit.hpp>
28 #include <system_error>
35 inline static int mkstemp(char* _template) { return _open(_mktemp(_template), _O_CREAT | _O_TEMPORARY, _S_IREAD | _S_IWRITE); }
41 using File = realm::util::File;
47 uint8_t value_of_hex_digit(char hex_digit)
49 if (hex_digit >= '0' && hex_digit <= '9') {
50 return hex_digit - '0';
51 } else if (hex_digit >= 'A' && hex_digit <= 'F') {
52 return 10 + hex_digit - 'A';
53 } else if (hex_digit >= 'a' && hex_digit <= 'f') {
54 return 10 + hex_digit - 'a';
56 throw std::invalid_argument("Cannot get the value of a character that isn't a hex digit.");
60 bool filename_is_reserved(const std::string& filename) {
61 return (filename == "." || filename == "..");
64 bool character_is_unreserved(char character)
66 bool is_capital_letter = (character >= 'A' && character <= 'Z');
67 bool is_lowercase_letter = (character >= 'a' && character <= 'z');
68 bool is_number = (character >= '0' && character <= '9');
69 bool is_allowed_symbol = (character == '-' || character == '_' || character == '.');
70 return is_capital_letter || is_lowercase_letter || is_number || is_allowed_symbol;
73 char decoded_char_for(const std::string& percent_encoding, size_t index)
75 if (index+2 >= percent_encoding.length()) {
76 throw std::invalid_argument("Malformed string: not enough characters after '%' before end of string.");
78 REALM_ASSERT(percent_encoding[index] == '%');
79 return (16*value_of_hex_digit(percent_encoding[index + 1])) + value_of_hex_digit(percent_encoding[index + 2]);
82 } // (anonymous namespace)
86 std::string make_percent_encoded_string(const std::string& raw_string)
89 buffer.reserve(raw_string.size());
90 for (size_t i=0; i<raw_string.size(); i++) {
91 unsigned char character = raw_string[i];
92 if (character_is_unreserved(character)) {
93 buffer.push_back(character);
95 buffer.resize(buffer.size() + 3);
96 // Format string must resolve to exactly 3 characters.
97 sprintf(&buffer.back() - 2, "%%%2X", character);
103 std::string make_raw_string(const std::string& percent_encoded_string)
106 size_t input_len = percent_encoded_string.length();
107 buffer.reserve(input_len);
109 while (idx < input_len) {
110 char current = percent_encoded_string[idx];
111 if (current == '%') {
113 buffer.push_back(decoded_char_for(percent_encoded_string, idx));
116 // No need to decode. +1.
117 if (!character_is_unreserved(current)) {
118 throw std::invalid_argument("Input string is invalid: contains reserved characters.");
120 buffer.push_back(current);
127 std::string file_path_by_appending_component(const std::string& path, const std::string& component, FilePathType path_type)
129 // FIXME: Does this have to be changed to accomodate Windows platforms?
131 buffer.reserve(2 + path.length() + component.length());
133 std::string terminal = "";
134 if (path_type == FilePathType::Directory && component[component.length() - 1] != '/') {
137 char path_last = path[path.length() - 1];
138 char component_first = component[0];
139 if (path_last == '/' && component_first == '/') {
140 buffer.append(component.substr(1));
141 buffer.append(terminal);
142 } else if (path_last == '/' || component_first == '/') {
143 buffer.append(component);
144 buffer.append(terminal);
147 buffer.append(component);
148 buffer.append(terminal);
153 std::string file_path_by_appending_extension(const std::string& path, const std::string& extension)
156 buffer.reserve(1 + path.length() + extension.length());
158 char path_last = path[path.length() - 1];
159 char extension_first = extension[0];
160 if (path_last == '.' && extension_first == '.') {
161 buffer.append(extension.substr(1));
162 } else if (path_last == '.' || extension_first == '.') {
163 buffer.append(extension);
166 buffer.append(extension);
171 std::string create_timestamped_template(const std::string& prefix, int wildcard_count)
173 constexpr int WILDCARD_MAX = 20;
174 constexpr int WILDCARD_MIN = 6;
175 wildcard_count = std::min(WILDCARD_MAX, std::max(WILDCARD_MIN, wildcard_count));
176 std::time_t time = std::time(nullptr);
177 std::stringstream stream;
178 stream << prefix << "-" << util::put_time(time, "%Y%m%d-%H%M%S") << "-" << std::string(wildcard_count, 'X');
182 std::string reserve_unique_file_name(const std::string& path, const std::string& template_string)
184 REALM_ASSERT_DEBUG(template_string.find("XXXXXX") != std::string::npos);
185 std::string path_buffer = file_path_by_appending_component(path, template_string, FilePathType::File);
186 int fd = mkstemp(&path_buffer[0]);
189 throw std::system_error(err, std::system_category());
191 // Remove the file so we can use the name for our own file.
194 _unlink(path_buffer.c_str());
197 unlink(path_buffer.c_str());
204 constexpr const char SyncFileManager::c_sync_directory[];
205 constexpr const char SyncFileManager::c_utility_directory[];
206 constexpr const char SyncFileManager::c_recovery_directory[];
207 constexpr const char SyncFileManager::c_metadata_directory[];
208 constexpr const char SyncFileManager::c_metadata_realm[];
209 constexpr const char SyncFileManager::c_user_info_file[];
211 std::string SyncFileManager::get_special_directory(std::string directory_name) const
213 auto dir_path = file_path_by_appending_component(get_base_sync_directory(),
215 util::FilePathType::Directory);
216 util::try_make_dir(dir_path);
220 std::string SyncFileManager::get_base_sync_directory() const
222 auto sync_path = file_path_by_appending_component(m_base_path,
224 util::FilePathType::Directory);
225 util::try_make_dir(sync_path);
229 std::string SyncFileManager::user_directory(const std::string& local_identity,
230 util::Optional<SyncUserIdentifier> user_info) const
232 REALM_ASSERT(local_identity.length() > 0);
233 std::string escaped = util::make_percent_encoded_string(local_identity);
234 if (filename_is_reserved(escaped))
235 throw std::invalid_argument("A user can't have an identifier reserved by the filesystem.");
237 auto user_path = file_path_by_appending_component(get_base_sync_directory(),
239 util::FilePathType::Directory);
240 bool dir_created = util::try_make_dir(user_path);
241 if (dir_created && user_info) {
242 // Add a text file in the user directory containing the user identity, for backup purposes.
243 // Only do this the first time the directory is created.
244 auto info_path = util::file_path_by_appending_component(user_path, c_user_info_file);
245 std::ofstream info_file;
246 info_file.open(info_path.c_str());
247 if (info_file.is_open()) {
248 info_file << user_info->user_id << "\n" << user_info->auth_server_url << "\n";
255 void SyncFileManager::remove_user_directory(const std::string& local_identity) const
257 REALM_ASSERT(local_identity.length() > 0);
258 const auto& escaped = util::make_percent_encoded_string(local_identity);
259 if (filename_is_reserved(escaped))
260 throw std::invalid_argument("A user can't have an identifier reserved by the filesystem.");
262 auto user_path = file_path_by_appending_component(get_base_sync_directory(),
264 util::FilePathType::Directory);
265 util::try_remove_dir_recursive(user_path);
268 bool SyncFileManager::try_rename_user_directory(const std::string& old_name, const std::string& new_name) const
270 REALM_ASSERT_DEBUG(old_name.length() > 0 && new_name.length() > 0);
271 const auto& old_name_escaped = util::make_percent_encoded_string(old_name);
272 const auto& new_name_escaped = util::make_percent_encoded_string(new_name);
273 const std::string& base = get_base_sync_directory();
274 if (filename_is_reserved(old_name_escaped) || filename_is_reserved(new_name_escaped))
275 throw std::invalid_argument("A user directory can't be renamed using a reserved identifier.");
277 const auto& old_path = file_path_by_appending_component(base, old_name_escaped, util::FilePathType::Directory);
278 const auto& new_path = file_path_by_appending_component(base, new_name_escaped, util::FilePathType::Directory);
281 File::move(old_path, new_path);
282 } catch (File::NotFound const&) {
288 bool SyncFileManager::remove_realm(const std::string& absolute_path) const
290 REALM_ASSERT(absolute_path.length() > 0);
292 // Remove the Realm file (e.g. "example.realm").
293 success = File::try_remove(absolute_path);
294 // Remove the lock file (e.g. "example.realm.lock").
295 auto lock_path = util::file_path_by_appending_extension(absolute_path, "lock");
296 success = File::try_remove(lock_path);
297 // Remove the management directory (e.g. "example.realm.management").
298 auto management_path = util::file_path_by_appending_extension(absolute_path, "management");
300 util::try_remove_dir_recursive(management_path);
302 catch (File::AccessError const&) {
308 bool SyncFileManager::copy_realm_file(const std::string& old_path, const std::string& new_path) const
310 REALM_ASSERT(old_path.length() > 0);
312 if (File::exists(new_path)) {
315 File::copy(old_path, new_path);
317 catch (File::NotFound const&) {
320 catch (File::AccessError const&) {
326 bool SyncFileManager::remove_realm(const std::string& local_identity, const std::string& raw_realm_path) const
328 REALM_ASSERT(local_identity.length() > 0);
329 REALM_ASSERT(raw_realm_path.length() > 0);
330 if (filename_is_reserved(local_identity) || filename_is_reserved(raw_realm_path))
331 throw std::invalid_argument("A user or Realm can't have an identifier reserved by the filesystem.");
333 auto escaped = util::make_percent_encoded_string(raw_realm_path);
334 auto realm_path = util::file_path_by_appending_component(user_directory(local_identity), escaped);
335 return remove_realm(realm_path);
338 std::string SyncFileManager::path(const std::string& local_identity, const std::string& raw_realm_path,
339 util::Optional<SyncUserIdentifier> user_info) const
341 REALM_ASSERT(local_identity.length() > 0);
342 REALM_ASSERT(raw_realm_path.length() > 0);
343 if (filename_is_reserved(local_identity) || filename_is_reserved(raw_realm_path))
344 throw std::invalid_argument("A user or Realm can't have an identifier reserved by the filesystem.");
346 auto escaped = util::make_percent_encoded_string(raw_realm_path);
347 auto realm_path = util::file_path_by_appending_component(user_directory(local_identity, user_info), escaped);
351 std::string SyncFileManager::metadata_path() const
353 auto dir_path = file_path_by_appending_component(get_utility_directory(),
354 c_metadata_directory,
355 util::FilePathType::Directory);
356 util::try_make_dir(dir_path);
357 return util::file_path_by_appending_component(dir_path, c_metadata_realm);
360 bool SyncFileManager::remove_metadata_realm() const
362 auto dir_path = file_path_by_appending_component(get_utility_directory(),
363 c_metadata_directory,
364 util::FilePathType::Directory);
366 util::try_remove_dir_recursive(dir_path);
369 catch (File::AccessError const&) {