| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "webkit/fileapi/obfuscated_name_map.h" |
| 6 |
| 7 #if defined(OS_WIN) |
| 8 #include <errno.h> |
| 9 #endif |
| 10 |
| 11 #include "base/file_util.h" |
| 12 #include "base/format_macros.h" |
| 13 #include "base/logging.h" |
| 14 #include "base/stringprintf.h" |
| 15 #include "base/sys_string_conversions.h" |
| 16 |
| 17 namespace fileapi { |
| 18 |
| 19 ObfuscatedNameMap::ObfuscatedNameMap() |
| 20 : directory_(), |
| 21 map_valid_(false), |
| 22 map_next_valid_(false) { |
| 23 } |
| 24 |
| 25 void ObfuscatedNameMap::Init(const FilePath& directory) { |
| 26 Rollback(); |
| 27 map_valid_ = false; |
| 28 map_.clear(); |
| 29 map_next_valid_ = false; |
| 30 map_next_.clear(); |
| 31 directory_ = directory; |
| 32 } |
| 33 |
| 34 PlatformFileError ObfuscatedNameMap::ClearNameFromObfuscatedName( |
| 35 StringType obfuscated_name, |
| 36 StringType* clear_name) { |
| 37 CHECK(clear_name); |
| 38 PlatformFileError error = LoadFromFileIfNeeded(); |
| 39 if (base::PLATFORM_FILE_OK != error) |
| 40 return error; |
| 41 MapType::iterator found = map_.find(obfuscated_name); |
| 42 if (found == map_.end()) |
| 43 return base::PLATFORM_FILE_ERROR_NOT_FOUND; |
| 44 *clear_name = found->second; |
| 45 return base::PLATFORM_FILE_OK; |
| 46 } |
| 47 |
| 48 StringType ObfuscatedNameMap::ObfuscatedNameFromClearName( |
| 49 StringType clear_name) { |
| 50 // FIXME: This is a hack to get around the fact that PRIxS produces an 8-bit |
| 51 // string on all platforms, and the macro that would fix that gets evaluated |
| 52 // *before* PRIxS does, so it can't be used. |
| 53 FilePath temp; |
| 54 return base::StringPrintf( |
| 55 temp.AppendASCII("%08" PRIxS).BaseName().value().c_str(), |
| 56 hash(FilePath(clear_name))); |
| 57 } |
| 58 |
| 59 PlatformFileError ObfuscatedNameMap::PrepareAdd( |
| 60 const StringType& clear_name, const StringType& obfuscated_name) { |
| 61 PlatformFileError error = InitMapNext(); |
| 62 if (base::PLATFORM_FILE_OK != error) |
| 63 return error; |
| 64 #ifndef NDEBUG |
| 65 StringType test_name(ObfuscatedNameFromClearName(clear_name)); |
| 66 CHECK(test_name == obfuscated_name); |
| 67 #endif |
| 68 if (map_next_.find(obfuscated_name) != map_next_.end()) |
| 69 return base::PLATFORM_FILE_ERROR_EXISTS; |
| 70 map_next_[obfuscated_name] = clear_name; |
| 71 return WriteToNextFile(); |
| 72 } |
| 73 |
| 74 PlatformFileError ObfuscatedNameMap::PrepareDelete( |
| 75 const StringType& clear_name, const StringType& obfuscated_name) { |
| 76 PlatformFileError error = InitMapNext(); |
| 77 if (base::PLATFORM_FILE_OK != error) |
| 78 return error; |
| 79 #ifndef NDEBUG |
| 80 StringType test_name(ObfuscatedNameFromClearName(clear_name)); |
| 81 CHECK(test_name == obfuscated_name); |
| 82 #endif |
| 83 |
| 84 MapType::iterator found = map_next_.find(obfuscated_name); |
| 85 if (found == map_next_.end()) |
| 86 return base::PLATFORM_FILE_ERROR_NOT_FOUND; |
| 87 map_next_.erase(found); |
| 88 return WriteToNextFile(); |
| 89 } |
| 90 |
| 91 PlatformFileError ObfuscatedNameMap::PrepareMove( |
| 92 const StringType& old_clear_name, |
| 93 const StringType& old_obfuscated_name, |
| 94 const StringType& new_clear_name, |
| 95 const StringType& new_obfuscated_name) { |
| 96 PlatformFileError error = InitMapNext(); |
| 97 if (base::PLATFORM_FILE_OK != error) |
| 98 return error; |
| 99 #ifndef NDEBUG |
| 100 StringType test_name(ObfuscatedNameFromClearName(old_clear_name)); |
| 101 CHECK(test_name == old_obfuscated_name); |
| 102 test_name = ObfuscatedNameFromClearName(new_clear_name); |
| 103 CHECK(test_name == new_obfuscated_name); |
| 104 #endif |
| 105 if (map_next_.find(new_obfuscated_name) != map_next_.end()) |
| 106 return base::PLATFORM_FILE_ERROR_EXISTS; |
| 107 MapType::iterator old_found = map_next_.find(old_obfuscated_name); |
| 108 if (old_found == map_next_.end()) |
| 109 return base::PLATFORM_FILE_ERROR_NOT_FOUND; |
| 110 map_next_.erase(old_found); |
| 111 map_next_[new_obfuscated_name] = new_clear_name; |
| 112 return WriteToNextFile(); |
| 113 } |
| 114 |
| 115 PlatformFileError ObfuscatedNameMap::Commit() { |
| 116 CHECK(map_next_valid_); |
| 117 if (file_util::Move(GetDictionaryNextLocation(), GetDictionaryLocation())) { |
| 118 map_.swap(map_next_); |
| 119 map_valid_ = map_next_valid_; |
| 120 map_next_valid_ = false; |
| 121 map_next_.clear(); |
| 122 return base::PLATFORM_FILE_OK; |
| 123 } else { |
| 124 return base::PLATFORM_FILE_ERROR_FAILED; |
| 125 } |
| 126 } |
| 127 |
| 128 void ObfuscatedNameMap::Rollback() { |
| 129 if (map_next_valid_) { |
| 130 map_next_valid_ = false; |
| 131 map_next_.clear(); |
| 132 // The file may or may not exist; we delete it and ignore errors. |
| 133 file_util::Delete(GetDictionaryNextLocation(), false); |
| 134 } |
| 135 } |
| 136 |
| 137 size_t ObfuscatedNameMap::hash(const FilePath& f) { |
| 138 // TODO(ericu): This is temporary--find a real SHA1 implementation or the |
| 139 // like somewhere in our existing codebase. |
| 140 #if defined(COMPILER_GCC) |
| 141 struct __gnu__cxx::hash<FilePath> gcc_hash; |
| 142 return gcc_hash(f); |
| 143 #elif defined(COMPILER_MSVC) |
| 144 return stdext::hash_value(f); |
| 145 #else |
| 146 #error // Must define hashing function |
| 147 #endif |
| 148 } |
| 149 |
| 150 FilePath ObfuscatedNameMap::GetDictionaryLocation() { |
| 151 return directory_.Append(StringType(DICTIONARY_NAME)); |
| 152 } |
| 153 |
| 154 FilePath ObfuscatedNameMap::GetDictionaryNextLocation() { |
| 155 return directory_.Append(StringType(DICTIONARY_NEXT_NAME)); |
| 156 } |
| 157 |
| 158 PlatformFileError ObfuscatedNameMap::LoadFromFileIfNeeded() { |
| 159 if (map_valid_) |
| 160 return base::PLATFORM_FILE_OK; |
| 161 |
| 162 PlatformFileError error = RecoverIfNeeded(); |
| 163 if (base::PLATFORM_FILE_OK != error) |
| 164 return error; |
| 165 |
| 166 if (!file_util::PathExists(GetDictionaryLocation())) { |
| 167 map_valid_ = true; // It's just empty. |
| 168 return base::PLATFORM_FILE_OK; |
| 169 } |
| 170 |
| 171 std::string contents; |
| 172 if (!file_util::ReadFileToString(GetDictionaryLocation(), &contents)) |
| 173 return base::PLATFORM_FILE_ERROR_FAILED; |
| 174 StringType data; |
| 175 #if defined(OS_POSIX) |
| 176 data = contents; |
| 177 #elif defined(OS_WIN) |
| 178 errno_t local_errno; |
| 179 size_t size = 0; |
| 180 |
| 181 local_errno = mbstowcs_s(&size, NULL, 0, contents.c_str(), 0); |
| 182 // FIXME: If either of these two happen, the dictionary is corrupt or |
| 183 // otherwise unusable. We should probably log an error and continue, but I |
| 184 // won't do that yet. |
| 185 // Max entries + entry length + a little padding. |
| 186 if (local_errno || size > 4000 * (256 + 120)) |
| 187 return base::PLATFORM_FILE_ERROR_FAILED; |
| 188 scoped_ptr<wchar_t> buffer(new wchar_t[size]); |
| 189 local_errno = |
| 190 mbstowcs_s(&size, buffer.get(), size, contents.c_str(), size - 1); |
| 191 if (local_errno) { |
| 192 return base::PLATFORM_FILE_ERROR_FAILED; // This should never happen. |
| 193 } |
| 194 data = buffer.get(); |
| 195 #else |
| 196 #error // Must define the appropriate conversion here. |
| 197 #endif |
| 198 LoadMapFromString(&map_, data); |
| 199 return base::PLATFORM_FILE_OK; |
| 200 } |
| 201 |
| 202 void ObfuscatedNameMap::LoadMapFromString( |
| 203 MapType* map, const StringType& serialized) { |
| 204 // Format: ([a-z0-0]+:[$legal_set_of_filename_chars]+:\n)* |
| 205 // Empty is legal; it's easier than deleting the dict when we remove the |
| 206 // last file from the dir, probably? |
| 207 // Note that ':' is the separator *and* the terminator [along with the |
| 208 // newline], since a newline could legally be in the filename. |
| 209 StringType hash, name; |
| 210 MapType temp_map; |
| 211 size_t i; |
| 212 for (i = 0; i < serialized.length(); ++i) { |
| 213 size_t hash_start = i; |
| 214 i = serialized.find(SEPARATOR, i); |
| 215 if (i == StringType::npos) |
| 216 break; |
| 217 size_t hash_end = i; |
| 218 size_t name_start = i + SEPARATOR_LENGTH; |
| 219 size_t name_end = serialized.find(TERMINATOR, name_start); |
| 220 if (name_end == StringType::npos) |
| 221 break; // TODO(ericu): Log an error here, but continue? |
| 222 i = name_end + TERMINATOR_LENGTH; |
| 223 hash = serialized.substr(hash_start, hash_end - hash_start); |
| 224 name = serialized.substr(name_start, name_end - name_start); |
| 225 temp_map[hash] = name; |
| 226 } |
| 227 // if (i != serialized.length()) |
| 228 // ; // TODO(ericu): Log an error here, but continue? |
| 229 map->swap(temp_map); |
| 230 } |
| 231 |
| 232 StringType ObfuscatedNameMap::ConvertMapToString(const MapType& map) { |
| 233 StringType serialized; |
| 234 for (MapType::const_iterator iter = map.begin(); |
| 235 iter != map.end(); ++iter) { |
| 236 serialized += iter->first + SEPARATOR + iter->second + TERMINATOR; |
| 237 } |
| 238 return serialized; |
| 239 } |
| 240 |
| 241 PlatformFileError ObfuscatedNameMap::InitMapNext() { |
| 242 CHECK(!map_next_valid_); |
| 243 PlatformFileError error = LoadFromFileIfNeeded(); |
| 244 if (base::PLATFORM_FILE_OK != error) |
| 245 return error; |
| 246 map_next_ = map_; |
| 247 map_next_valid_ = true; |
| 248 return base::PLATFORM_FILE_OK; |
| 249 } |
| 250 |
| 251 PlatformFileError ObfuscatedNameMap::RecoverIfNeeded() { |
| 252 // TODO(ericu): if DICT_NEXT doesn't exist, return base::PLATFORM_FILE_OK. |
| 253 // TODO(ericu): read map_ from DICT, map_next from DICT_NEXT. |
| 254 // TODO(ericu): find the *one* element that differs. |
| 255 // TODO(ericu): if it's not exactly one, goto handle_error |
| 256 // TODO(ericu): |
| 257 // if it's a deletion: |
| 258 // if the file's gone, commit the dict, else revert the dict |
| 259 // else if it's an addition: |
| 260 // if the file's there, commit the dict, else revert the dict |
| 261 // else // it's a move: |
| 262 // if the old file's gone and the new file's there, commit the dict. |
| 263 // if the old file's there and the new file's not, revert the dict. |
| 264 // else goto handle_error |
| 265 // return base::PLATFORM_FILE_OK; |
| 266 // handle_error: either return an error here, or revert the dict so that |
| 267 // progress can be made; it's a policy decision. |
| 268 // either way, make sure that map_valid_ is valid. |
| 269 // |
| 270 return base::PLATFORM_FILE_OK; |
| 271 } |
| 272 |
| 273 PlatformFileError ObfuscatedNameMap::WriteToNextFile() { |
| 274 StringType contents = ConvertMapToString(map_next_); |
| 275 std::string data; |
| 276 #if defined(OS_POSIX) |
| 277 data.swap(contents); |
| 278 #elif defined(OS_WIN) |
| 279 data = base::SysWideToUTF8(contents); |
| 280 #else |
| 281 #error // Must define the appropriate conversion here. |
| 282 #endif |
| 283 size_t bytes_written = 0; |
| 284 FilePath location = GetDictionaryNextLocation(); |
| 285 while (bytes_written < data.length()) { |
| 286 int write_response = file_util::WriteFile( |
| 287 location, data.c_str() + bytes_written, data.length()); |
| 288 if (write_response <= 0) { |
| 289 if (bytes_written) |
| 290 file_util::Delete(GetDictionaryNextLocation(), false /* recursive */); |
| 291 return base::PLATFORM_FILE_ERROR_FAILED; |
| 292 } |
| 293 bytes_written += write_response; |
| 294 } |
| 295 return base::PLATFORM_FILE_OK; |
| 296 } |
| 297 |
| 298 const StringType::value_type ObfuscatedNameMap::DICTIONARY_NAME[] = |
| 299 FILE_PATH_LITERAL("dict"); |
| 300 const StringType::value_type ObfuscatedNameMap::DICTIONARY_NEXT_NAME[] = |
| 301 FILE_PATH_LITERAL("dict_next"); |
| 302 const StringType::value_type ObfuscatedNameMap::SEPARATOR[] = |
| 303 FILE_PATH_LITERAL(":"); |
| 304 const size_t ObfuscatedNameMap::SEPARATOR_LENGTH = 1; |
| 305 const StringType::value_type ObfuscatedNameMap::TERMINATOR[] = |
| 306 FILE_PATH_LITERAL(":\n"); |
| 307 const size_t ObfuscatedNameMap::TERMINATOR_LENGTH = 2; |
| 308 |
| 309 } // namespace fileapi |
| OLD | NEW |