added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / ObjectStore / src / sync / impl / sync_file.cpp
1 ////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2016 Realm Inc.
4 //
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
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
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.
16 //
17 ////////////////////////////////////////////////////////////////////////////
18
19 #include "sync/impl/sync_file.hpp"
20
21 #include "util/time.hpp"
22
23 #include <realm/util/file.hpp>
24 #include <realm/util/scope_exit.hpp>
25
26 #include <iomanip>
27 #include <sstream>
28 #include <system_error>
29 #include <fstream>
30
31 #ifdef _WIN32
32 #include <io.h>
33 #include <fcntl.h>
34
35 inline static int mkstemp(char* _template) { return _open(_mktemp(_template), _O_CREAT | _O_TEMPORARY, _S_IREAD | _S_IWRITE); }
36 #else
37 #include <unistd.h>
38 #endif
39
40
41 using File = realm::util::File;
42
43 namespace realm {
44
45 namespace {
46
47 uint8_t value_of_hex_digit(char hex_digit)
48 {
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';
55     } else {
56         throw std::invalid_argument("Cannot get the value of a character that isn't a hex digit.");
57     }
58 }
59
60 bool filename_is_reserved(const std::string& filename) {
61     return (filename == "." || filename == "..");
62 }
63
64 bool character_is_unreserved(char character)
65 {
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;
71 }
72
73 char decoded_char_for(const std::string& percent_encoding, size_t index)
74 {
75     if (index+2 >= percent_encoding.length()) {
76         throw std::invalid_argument("Malformed string: not enough characters after '%' before end of string.");
77     }
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]);
80 }
81
82 } // (anonymous namespace)
83
84 namespace util {
85
86 std::string make_percent_encoded_string(const std::string& raw_string)
87 {
88     std::string buffer;
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);
94         } else {
95             buffer.resize(buffer.size() + 3);
96             // Format string must resolve to exactly 3 characters.
97             sprintf(&buffer.back() - 2, "%%%2X", character);
98         }
99     }
100     return buffer;
101 }
102
103 std::string make_raw_string(const std::string& percent_encoded_string)
104 {
105     std::string buffer;
106     size_t input_len = percent_encoded_string.length();
107     buffer.reserve(input_len);
108     size_t idx = 0;
109     while (idx < input_len) {
110         char current = percent_encoded_string[idx];
111         if (current == '%') {
112             // Decode. +3.
113             buffer.push_back(decoded_char_for(percent_encoded_string, idx));
114             idx += 3;
115         } else {
116             // No need to decode. +1.
117             if (!character_is_unreserved(current)) {
118                 throw std::invalid_argument("Input string is invalid: contains reserved characters.");
119             }
120             buffer.push_back(current);
121             idx++;
122         }
123     }
124     return buffer;
125 }
126
127 std::string file_path_by_appending_component(const std::string& path, const std::string& component, FilePathType path_type)
128 {
129     // FIXME: Does this have to be changed to accomodate Windows platforms?
130     std::string buffer;
131     buffer.reserve(2 + path.length() + component.length());
132     buffer.append(path);
133     std::string terminal = "";
134     if (path_type == FilePathType::Directory && component[component.length() - 1] != '/') {
135         terminal = "/";
136     }
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);
145     } else {
146         buffer.append("/");
147         buffer.append(component);
148         buffer.append(terminal);
149     }
150     return buffer;
151 }
152
153 std::string file_path_by_appending_extension(const std::string& path, const std::string& extension)
154 {
155     std::string buffer;
156     buffer.reserve(1 + path.length() + extension.length());
157     buffer.append(path);
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);
164     } else {
165         buffer.append(".");
166         buffer.append(extension);
167     }
168     return buffer;
169 }
170
171 std::string create_timestamped_template(const std::string& prefix, int wildcard_count)
172 {
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');
179     return stream.str();
180 }
181
182 std::string reserve_unique_file_name(const std::string& path, const std::string& template_string)
183 {
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]);
187     if (fd < 0) {
188         int err = errno;
189         throw std::system_error(err, std::system_category());
190     }
191     // Remove the file so we can use the name for our own file.
192 #ifdef _WIN32
193     _close(fd);
194     _unlink(path_buffer.c_str());
195 #else
196     close(fd);
197     unlink(path_buffer.c_str());
198 #endif
199     return path_buffer;
200 }
201
202 } // util
203
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[];
210
211 std::string SyncFileManager::get_special_directory(std::string directory_name) const
212 {
213     auto dir_path = file_path_by_appending_component(get_base_sync_directory(),
214                                                      directory_name,
215                                                      util::FilePathType::Directory);
216     util::try_make_dir(dir_path);
217     return dir_path;
218 }
219
220 std::string SyncFileManager::get_base_sync_directory() const
221 {
222     auto sync_path = file_path_by_appending_component(m_base_path,
223                                                       c_sync_directory,
224                                                       util::FilePathType::Directory);
225     util::try_make_dir(sync_path);
226     return sync_path;
227 }
228
229 std::string SyncFileManager::user_directory(const std::string& local_identity,
230                                             util::Optional<SyncUserIdentifier> user_info) const
231 {
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.");
236
237     auto user_path = file_path_by_appending_component(get_base_sync_directory(),
238                                                       escaped,
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";
249             info_file.close();
250         }
251     }
252     return user_path;
253 }
254
255 void SyncFileManager::remove_user_directory(const std::string& local_identity) const
256 {
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.");
261
262     auto user_path = file_path_by_appending_component(get_base_sync_directory(),
263                                                       escaped,
264                                                       util::FilePathType::Directory);
265     util::try_remove_dir_recursive(user_path);
266 }
267
268 bool SyncFileManager::try_rename_user_directory(const std::string& old_name, const std::string& new_name) const
269 {
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.");
276
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);
279
280     try {
281         File::move(old_path, new_path);
282     } catch (File::NotFound const&) {
283         return false;
284     }
285     return true;
286 }
287
288 bool SyncFileManager::remove_realm(const std::string& absolute_path) const
289 {
290     REALM_ASSERT(absolute_path.length() > 0);
291     bool success = true;
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");
299     try {
300         util::try_remove_dir_recursive(management_path);
301     }
302     catch (File::AccessError const&) {
303         success = false;
304     }
305     return success;
306 }
307
308 bool SyncFileManager::copy_realm_file(const std::string& old_path, const std::string& new_path) const
309 {
310     REALM_ASSERT(old_path.length() > 0);
311     try {
312         if (File::exists(new_path)) {
313             return false;
314         }
315         File::copy(old_path, new_path);
316     }
317     catch (File::NotFound const&) {
318         return false;
319     }
320     catch (File::AccessError const&) {
321         return false;
322     }
323     return true;
324 }
325
326 bool SyncFileManager::remove_realm(const std::string& local_identity, const std::string& raw_realm_path) const
327 {
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.");
332
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);
336 }
337
338 std::string SyncFileManager::path(const std::string& local_identity, const std::string& raw_realm_path,
339                                   util::Optional<SyncUserIdentifier> user_info) const
340 {
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.");
345
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);
348     return realm_path;
349 }
350
351 std::string SyncFileManager::metadata_path() const
352 {
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);
358 }
359
360 bool SyncFileManager::remove_metadata_realm() const
361 {
362     auto dir_path = file_path_by_appending_component(get_utility_directory(),
363                                                      c_metadata_directory,
364                                                      util::FilePathType::Directory);
365     try {
366         util::try_remove_dir_recursive(dir_path);
367         return true;
368     }
369     catch (File::AccessError const&) {
370         return false;
371     }
372 }
373
374 } // realm